From 88ba89dbbc8ccf62ae7a9a27a68da4ab9bc585ea Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 26 Jul 2022 14:48:26 +0200 Subject: [PATCH] STEP: Implementation ported from BambuStudio: CMake handling is different STEP: Removed preprocessing stage STEP: Small refactoring STEP: Bigger refactoring STEP: Changed naming on loaded object and volumes: If the STEP contains exactly one named volume, the object and its first volume will both have that name. Otherwise, filename w/o suffix is used as object name and volumes are named using names from the STEP (if there is none, untranslated "PartN" string is used). STEP: Load the libraries dynamically on Win wip --- deps/CMakeLists.txt | 2 + deps/OCCT/OCCT.cmake | 22 ++++ src/CMakeLists.txt | 5 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Format/STEP.cpp | 110 +++++++++++++++++ src/libslic3r/Format/STEP.hpp | 19 +++ src/libslic3r/Model.cpp | 3 + src/libslic3r/TriangleMesh.cpp | 1 + src/occt_wrapper/CMakeLists.txt | 51 ++++++++ src/occt_wrapper/OCCTWrapper.cpp | 201 +++++++++++++++++++++++++++++++ src/occt_wrapper/OCCTWrapper.hpp | 27 +++++ src/slic3r/GUI/GUI_App.cpp | 3 +- src/slic3r/GUI/GUI_App.hpp | 1 + 13 files changed, 443 insertions(+), 4 deletions(-) create mode 100644 deps/OCCT/OCCT.cmake create mode 100644 src/libslic3r/Format/STEP.cpp create mode 100644 src/libslic3r/Format/STEP.hpp create mode 100644 src/occt_wrapper/CMakeLists.txt create mode 100644 src/occt_wrapper/OCCTWrapper.cpp create mode 100644 src/occt_wrapper/OCCTWrapper.hpp diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 56a8fa429..0fe4e41c7 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -189,6 +189,7 @@ endif () include(JPEG/JPEG.cmake) include(TIFF/TIFF.cmake) include(wxWidgets/wxWidgets.cmake) +include(OCCT/OCCT.cmake) set(_dep_list dep_Boost @@ -200,6 +201,7 @@ set(_dep_list dep_OpenVDB dep_OpenCSG dep_CGAL + dep_OCCT ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} diff --git a/deps/OCCT/OCCT.cmake b/deps/OCCT/OCCT.cmake new file mode 100644 index 000000000..9981ac15e --- /dev/null +++ b/deps/OCCT/OCCT.cmake @@ -0,0 +1,22 @@ +prusaslicer_add_cmake_project(OCCT + #LMBBS: changed version to 7.6.2 + URL https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V7_6_2.zip + URL_HASH SHA256=c696b923593e8c18d059709717dbf155b3e72fdd283c8522047a790ec3a432c5 + + CMAKE_ARGS + -DINSTALL_DIR_LAYOUT=Unix # LMBBS + -DBUILD_LIBRARY_TYPE=Static + -DUSE_TK=OFF + -DUSE_TBB=OFF + -DUSE_FREETYPE=OFF + -DUSE_FFMPEG=OFF + -DUSE_VTK=OFF + -DUSE_FREETYPE=OFF + -DBUILD_MODULE_ApplicationFramework=OFF + #-DBUILD_MODULE_DataExchange=OFF + -DBUILD_MODULE_Draw=OFF + -DBUILD_MODULE_FoundationClasses=OFF + -DBUILD_MODULE_ModelingAlgorithms=OFF + -DBUILD_MODULE_ModelingData=OFF + -DBUILD_MODULE_Visualization=OFF +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cfee06342..2ea8eaa04 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,11 +15,9 @@ add_subdirectory(semver) add_subdirectory(libigl) add_subdirectory(hints) add_subdirectory(qoi) - -# Adding libnest2d project for bin packing... add_subdirectory(libnest2d) - add_subdirectory(libslic3r) +add_subdirectory(occt_wrapper) if (SLIC3R_GUI) add_subdirectory(imgui) @@ -127,6 +125,7 @@ if (NOT WIN32 AND NOT APPLE) endif () target_link_libraries(PrusaSlicer libslic3r cereal) + if (APPLE) # add_compile_options(-stdlib=libc++) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 41ad68db5..52ea9be08 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -94,6 +94,8 @@ add_library(libslic3r STATIC Format/STL.hpp Format/SL1.hpp Format/SL1.cpp + Format/STEP.hpp + Format/STEP.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp GCode/Thumbnails.cpp diff --git a/src/libslic3r/Format/STEP.cpp b/src/libslic3r/Format/STEP.cpp new file mode 100644 index 000000000..9b4d2c9ea --- /dev/null +++ b/src/libslic3r/Format/STEP.cpp @@ -0,0 +1,110 @@ +#include "STEP.hpp" +#include "occt_wrapper/OCCTWrapper.hpp" + +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMesh.hpp" + +#include +#include + +#ifdef _WIN32 + #include +#else + #include + #include +#endif + + +namespace Slic3r { + +LoadStepFn get_load_step_fn() +{ + static LoadStepFn load_step_fn = nullptr; + + if (!load_step_fn) { +#ifdef _WIN32 + HMODULE module = LoadLibraryW(L"OCCTWrapper.dll"); + if (module == NULL) + throw Slic3r::RuntimeError("Cannot load OCCTWrapper.dll"); + + try { + const char* fn_name = "load_step_internal"; + FARPROC farproc = GetProcAddress(module, fn_name); + if (! farproc) { + DWORD ec = GetLastError(); + throw Slic3r::RuntimeError(std::string("Cannot load function from OCCTWrapper.dll: ") + fn_name + + "\n\nError code: " + std::to_string(ec)); + } + load_step_fn = reinterpret_cast(farproc); + } catch (const Slic3r::RuntimeError&) { + FreeLibrary(module); + throw; + } +#else + void *plugin_ptr = dlopen("OCCTWrapper.so", RTLD_NOW | RTLD_GLOBAL); + + if (plugin_ptr) { + load_step_fn = reinterpret_cast(dlsym(plugin_ptr, "load_step_internal")); + if (!load_step_fn) { + dlclose(plugin_ptr); + } + } +#endif + } + + return load_step_fn; +} + +bool load_step(const char *path, Model *model /*BBS:, ImportStepProgressFn proFn*/) +{ + OCCTResult occt_object; + + LoadStepFn load_step_fn = get_load_step_fn(); + + if (!load_step_fn) + return false; + + load_step_fn(path, &occt_object); + + assert(! occt_object.volumes.empty()); + + assert(boost::algorithm::iends_with(occt_object.object_name, ".stp") + || boost::algorithm::iends_with(occt_object.object_name, ".step")); + occt_object.object_name.erase(occt_object.object_name.find(".")); + assert(! occt_object.object_name.empty()); + + + ModelObject* new_object = model->add_object(); + new_object->input_file = path; + if (new_object->volumes.size() == 1 && ! occt_object.volumes.front().volume_name.empty()) + new_object->name = new_object->volumes.front()->name; + else + new_object->name = occt_object.object_name; + + + for (size_t i=0; iadd_volume(std::move(triangle_mesh)); + + new_volume->name = occt_object.volumes[i].volume_name.empty() + ? std::string("Part") + std::to_string(i+1) + : occt_object.volumes[i].volume_name; + new_volume->source.input_file = path; + new_volume->source.object_idx = (int)model->objects.size() - 1; + new_volume->source.volume_idx = (int)new_object->volumes.size() - 1; + } + + return true; +} + +}; // namespace Slic3r diff --git a/src/libslic3r/Format/STEP.hpp b/src/libslic3r/Format/STEP.hpp new file mode 100644 index 000000000..8fbc604d8 --- /dev/null +++ b/src/libslic3r/Format/STEP.hpp @@ -0,0 +1,19 @@ +// Original implementation of STEP format import created by Bambulab. +// https://github.com/bambulab/BambuStudio +// Forked off commit 1555904, modified by Prusa Research. + +#ifndef slic3r_Format_STEP_hpp_ +#define slic3r_Format_STEP_hpp_ + +namespace Slic3r { + +class Model; + +//typedef std::function ImportStepProgressFn; + +// Load a step file into a provided model. +extern bool load_step(const char *path_str, Model *model /*LMBBS:, ImportStepProgressFn proFn = nullptr*/); + +}; // namespace Slic3r + +#endif /* slic3r_Format_STEP_hpp_ */ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index efc66f478..77011800d 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -13,6 +13,7 @@ #include "Format/OBJ.hpp" #include "Format/STL.hpp" #include "Format/3mf.hpp" +#include "Format/STEP.hpp" #include @@ -114,6 +115,8 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c result = load_stl(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); + else if (boost::algorithm::iends_with(input_file, ".step") || boost::algorithm::iends_with(input_file, ".stp")) + result = load_step(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); else if (boost::algorithm::iends_with(input_file, ".3mf")) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 93a09a0d9..807ebcc9a 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1370,4 +1370,5 @@ bool its_write_stl_binary(const char *file, const char *label, const std::vector return true; } + } // namespace Slic3r diff --git a/src/occt_wrapper/CMakeLists.txt b/src/occt_wrapper/CMakeLists.txt new file mode 100644 index 000000000..2629e1d59 --- /dev/null +++ b/src/occt_wrapper/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.13) +project(OCCTWrapper) + +add_library(OCCTWrapper SHARED OCCTWrapper.cpp) + +set_target_properties(OCCTWrapper + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/src" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/src" + PREFIX "" +) + +include(GenerateExportHeader) + +generate_export_header(OCCTWrapper) + +find_package(OpenCASCADE 7.6.2 REQUIRED) + +set(OCCT_LIBS + TKXDESTEP + TKSTEP + TKSTEP209 + TKSTEPAttr + TKSTEPBase + TKXCAF + TKXSBase + TKVCAF + TKCAF + TKLCAF + TKCDF + TKV3d + TKService + TKMesh + TKBO + TKPrim + TKHLR + TKShHealing + TKTopAlgo + TKGeomAlgo + TKBRep + TKGeomBase + TKG3d + TKG2d + TKMath + TKernel +) + +target_include_directories(OCCTWrapper PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(OCCTWrapper PUBLIC ${OpenCASCADE_INCLUDE_DIR}) +target_link_libraries(OCCTWrapper ${OCCT_LIBS}) + diff --git a/src/occt_wrapper/OCCTWrapper.cpp b/src/occt_wrapper/OCCTWrapper.cpp new file mode 100644 index 000000000..87f15e7f9 --- /dev/null +++ b/src/occt_wrapper/OCCTWrapper.cpp @@ -0,0 +1,201 @@ +#include "OCCTWrapper.hpp" + +#include "occtwrapper_export.h" + +#include + +#ifdef _WIN32 +#define DIR_SEPARATOR '\\' +#else +#define DIR_SEPARATOR '/' +#endif + +#include "STEPCAFControl_Reader.hxx" +#include "BRepMesh_IncrementalMesh.hxx" +#include "XCAFDoc_DocumentTool.hxx" +#include "XCAFDoc_ShapeTool.hxx" +#include "XCAFApp_Application.hxx" +#include "TopoDS_Builder.hxx" +#include "TopoDS.hxx" +#include "TDataStd_Name.hxx" +#include "BRepBuilderAPI_Transform.hxx" +#include "TopExp_Explorer.hxx" +#include "BRep_Tool.hxx" + +const double STEP_TRANS_CHORD_ERROR = 0.005; +const double STEP_TRANS_ANGLE_RES = 1; + +// const int LOAD_STEP_STAGE_READ_FILE = 0; +// const int LOAD_STEP_STAGE_GET_SOLID = 1; +// const int LOAD_STEP_STAGE_GET_MESH = 2; + +namespace Slic3r { + +struct NamedSolid { + NamedSolid(const TopoDS_Shape& s, + const std::string& n) : solid{s}, name{n} {} + const TopoDS_Shape solid; + const std::string name; +}; + +static void getNamedSolids(const TopLoc_Location& location, const Handle(XCAFDoc_ShapeTool) shapeTool, + const TDF_Label label, std::vector& namedSolids) +{ + TDF_Label referredLabel{label}; + if (shapeTool->IsReference(label)) + shapeTool->GetReferredShape(label, referredLabel); + + std::string name; + Handle(TDataStd_Name) shapeName; + if (referredLabel.FindAttribute(TDataStd_Name::GetID(), shapeName)) + name = TCollection_AsciiString(shapeName->Get()).ToCString(); + + TopLoc_Location localLocation = location * shapeTool->GetLocation(label); + TDF_LabelSequence components; + if (shapeTool->GetComponents(referredLabel, components)) { + for (Standard_Integer compIndex = 1; compIndex <= components.Length(); ++compIndex) { + getNamedSolids(localLocation, shapeTool, components.Value(compIndex), namedSolids); + } + } else { + TopoDS_Shape shape; + shapeTool->GetShape(referredLabel, shape); + TopAbs_ShapeEnum shape_type = shape.ShapeType(); + BRepBuilderAPI_Transform transform(shape, localLocation, Standard_True); + switch (shape_type) { + case TopAbs_COMPOUND: + namedSolids.emplace_back(TopoDS::Compound(transform.Shape()), name); + break; + case TopAbs_COMPSOLID: + namedSolids.emplace_back(TopoDS::CompSolid(transform.Shape()), name); + break; + case TopAbs_SOLID: + namedSolids.emplace_back(TopoDS::Solid(transform.Shape()), name); + break; + default: + break; + } + } +} + +extern "C" OCCTWRAPPER_EXPORT bool load_step_internal(const char *path, OCCTResult* res /*BBS:, ImportStepProgressFn proFn*/) +{ +try { + bool cb_cancel = false; + //if (proFn) { + // proFn(LOAD_STEP_STAGE_READ_FILE, 0, 1, cb_cancel); + // if (cb_cancel) + // return false; + //} + + + std::vector namedSolids; + Handle(TDocStd_Document) document; + Handle(XCAFApp_Application) application = XCAFApp_Application::GetApplication(); + application->NewDocument(path, document); + STEPCAFControl_Reader reader; + reader.SetNameMode(true); + //BBS: Todo, read file is slow which cause the progress_bar no update and gui no response + IFSelect_ReturnStatus stat = reader.ReadFile(path); + if (stat != IFSelect_RetDone || !reader.Transfer(document)) { + application->Close(document); + res->error_str = std::string{"Could not read '"} + path + "'"; + return false; + } + Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(document->Main()); + TDF_LabelSequence topLevelShapes; + shapeTool->GetFreeShapes(topLevelShapes); + + Standard_Integer topShapeLength = topLevelShapes.Length() + 1; + for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) { + //if (proFn) { + // proFn(LOAD_STEP_STAGE_GET_SOLID, iLabel, topShapeLength, cb_cancel); + // if (cb_cancel) { + // shapeTool.reset(nullptr); + // application->Close(document); + // return false; + // } + //} + getNamedSolids(TopLoc_Location{}, shapeTool, topLevelShapes.Value(iLabel), namedSolids); + } + + + + // Now the object name. Set it to filename without suffix. + // This will later be changed if only one volume is loaded. + const char *last_slash = strrchr(path, DIR_SEPARATOR); + std::string obj_name((last_slash == nullptr) ? path : last_slash + 1); + res->object_name = obj_name; + + for (size_t i = 0; i < namedSolids.size(); ++i) { + //BBS:if (proFn) { + // proFn(LOAD_STEP_STAGE_GET_MESH, i, namedSolids.size(), cb_cancel); + // if (cb_cancel) { + // model->delete_object(new_object); + // shapeTool.reset(nullptr); + // application->Close(document); + // return false; + // } + //} + + res->volumes.emplace_back(); + auto& vertices = res->volumes.back().vertices; + auto& indices = res->volumes.back().indices; + + BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true); + + for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + const int aNodeOffset = int(vertices.size()); + const TopoDS_Shape& aFace = anExpSF.Current(); + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc); + if (aTriangulation.IsNull()) + continue; + + // First copy vertices (will create duplicates). + gp_Trsf aTrsf = aLoc.Transformation(); + for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) { + gp_Pnt aPnt = aTriangulation->Node(aNodeIter); + aPnt.Transform(aTrsf); + vertices.push_back({float(aPnt.X()), float(aPnt.Y()), float(aPnt.Z())}); + } + // Now the indices. + 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) + std::swap(anId[1], anId[2]); + + // Account for the vertices we already have from previous faces. + // anId is 1-based index ! + indices.push_back({anId[0] - 1 + aNodeOffset, + anId[1] - 1 + aNodeOffset, + anId[2] - 1 + aNodeOffset}); + } + } + + res->volumes.back().volume_name = namedSolids[i].name; + + if (vertices.empty()) + res->volumes.pop_back(); + } + + shapeTool.reset(nullptr); + application->Close(document); + + if (res->volumes.empty()) + return false; +} catch (const std::exception& ex) { + res->error_str = ex.what(); + return false; +} catch (...) { + res->error_str = "An exception was thrown in load_step_internal."; + return false; +} + + return true; +} + +}; // namespace Slic3r diff --git a/src/occt_wrapper/OCCTWrapper.hpp b/src/occt_wrapper/OCCTWrapper.hpp new file mode 100644 index 000000000..e87becb70 --- /dev/null +++ b/src/occt_wrapper/OCCTWrapper.hpp @@ -0,0 +1,27 @@ + +#ifndef occtwrapper_OCCTWrapper_hpp_ +#define occtwrapper_OCCTWrapper_hpp_ + +#include +#include +#include + +namespace Slic3r { + +struct OCCTVolume { + std::string volume_name; + std::vector> vertices; + std::vector> indices; +}; + +struct OCCTResult { + std::string error_str; + std::string object_name; + std::vector volumes; +}; + +using LoadStepFn = bool (*)(const char *path, OCCTResult* occt_result); + +}; // namespace Slic3r + +#endif // occtwrapper_OCCTWrapper_hpp_ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5bc770b2c..6362dc609 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -482,10 +482,11 @@ struct FileWildcards { static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_STL */ { "STL files"sv, { ".stl"sv } }, /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, + /* FT_STEP */ { "STEP files"sv, { ".stp"sv, ".step"sv } }, /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, - /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } }, + /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv } }, /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index c60dc8d6f..8775a5f31 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -53,6 +53,7 @@ enum FileType { FT_STL, FT_OBJ, + FT_STEP, FT_AMF, FT_3MF, FT_GCODE,