Files
OrcaSlicer/src/libslic3r/Format/STEP.cpp
T
Ian Bassi 6d35ff6b9a Rewrite Fix Model on CGAL 6.2, fix Split to parts, remove Win10 SDK leftovers (#16366)
* Add CGAL-based mesh repair, drop Netfabb remnants

Add repair_mesh() in MeshRepair.cpp, a CGAL-based repair that replaces MeshBoolean::cgal::repair(). It welds cracks, cancels coincident triangles, fixes orientation, fills holes (including planar annular regions), removes local self-intersections, and unites crossing shells with libigl's exact boolean while following the nonzero winding rule used by slicing.

FixModelByCgal now repairs each volume in place without splitting it into parts, and reports whether the mesh was modified so unchanged meshes keep their painting and ids. The texture import repair prompt no longer depends on the Windows SDK, and the FIX_THROUGH_CGAL_ALWAYS conditionals are removed. The commented-out Netfabb/WIN10SDK build code is deleted.

libslic3r_cgal now builds with CGAL_LINKED_WITH_TBB and links TBB. Add docs/HLSD/mesh-repair.md and unit tests in test_mesh_repair.cpp.

* Parallelize repair while preserving separate shells

Repair connected mesh units and shells independently in parallel, keep crossing shells separate, and limit self-intersection remeshing to local defects. Update mesh-repair documentation and tests for the revised behavior.

* Make mesh repair deterministic and report its progress

Flags written from parallel loops were std::vector<bool>, whose neighbouring
bits share a word, so shells could be kept or dropped at random; they are now
std::vector<char>. Holes of up to 100 edges are filled with the search over all
triangles: the search restricted to a 3D Delaunay triangulation picks between
equally good fills differently from run to run.

Shells of fewer than four faces are dropped, as the volumes of two triangles
back to back need not cancel exactly, and a shell that self-intersection removal
changed is split again so the pieces it cut off are dropped too.

repair_mesh() takes a progress callback in place of the cancel callback, and
Fix Model reports each stage. The object's bounding box is now invalidated even
when a cancel or an error stops the repair after some volumes were replaced.

The edge-use sort, union-find and box-overlap code shared by several stages is
factored out, the topology analysed for welding is reused to build the work
units, the orientation pass checks only the edges it cut, and the output is
written from the shells without joining them first.

* Fix Split to parts crashing on flat pieces and staying offered after a repair

ModelVolume::split() added every piece to the object and then deleted the
pieces without a convex hull. When the first piece was flat, as often in a
triangle soup, that deleted the volume being split while split() and its
caller kept using it. A part made only of flat pieces lost all its volumes,
and a soup added thousands of pieces, saving a backup of the object for each
one it deleted. Pieces without a convex hull are now dropped before any joins
the object, the volume being split keeps the first remaining piece, and a part
with no solid piece is left whole. Other parts of the object without a convex
hull are no longer deleted as a side effect.

ModelVolume caches whether its mesh can be split, and set_mesh() kept the
cached value, so after Fix Model, Simplify or Emboss replaced a mesh, Split to
parts could still be offered for a single piece. set_mesh() and reset_mesh()
now clear the cache.

* Import STEP surface models with Split compound enabled

Splitting a compound kept only its solids, so a surface model, which OCCT
reads as a compound of loose faces, gave nothing to mesh and the import failed
with "Meshing of a model file failed or no valid shape". Each shell outside a
solid now becomes a part too, and loose faces stay together as one part, as
they do without splitting.

* Add Fix All for plates and re-enable --repair on the command line

ModelVolume::repair() applies repair_mesh() to a part and replaces the mesh
only when the repair changed it. Fix Model, the new Fix All and Fix All Plates
items of the plate context menu, and the command line's --repair option, which
had been commented out since the Win10 SDK repair was dropped, all use it.

With Keep painted feature after mesh change off, Fix Model cleared the painting
of every part of an object before repairing. Now only a part whose mesh changes
loses its painting, so fixing a whole project does not wipe it. The
notification counts the models that needed no repair instead of listing them as
repaired, and objects that did not change are not refreshed.
2026-10-12 21:59:17 -03:00

793 lines
30 KiB
C++

#include "../Model.hpp"
#include "../TriangleMesh.hpp"
#include "libslic3r/Exception.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Thread.hpp"
#include "STEP.hpp"
#include <boost/filesystem/path.hpp>
#include <ios>
#include <ostream>
#include <cstddef>
#include <Standard_Handle.hxx>
#include <Standard_TypeDef.hxx>
#include <TopAbs_ShapeEnum.hxx>
#include <atomic>
#include <IFSelect_ReturnStatus.hxx>
#include <boost/chrono/duration.hpp>
#include <cstring>
#include <Poly_Triangulation.hxx>
#include <cstdint>
#include <gp_Trsf.hxx>
#include <gp_Pnt.hxx>
#include <TopAbs_Orientation.hxx>
#include <Poly_Triangle.hxx>
#include <string>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <vector>
#include <utility>
#ifdef _WIN32
#define DIR_SEPARATOR '\\'
#else
#define DIR_SEPARATOR '/'
#endif
#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 "NCollection_Sequence.hxx"
#include "TDF_Label.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 "BRep_Tool.hxx"
#include "BRepTools.hxx"
#include <IMeshTools_Parameters.hxx>
namespace fs = boost::filesystem;
namespace Slic3r {
bool StepPreProcessor::preprocess(const char* path, std::string &output_path)
{
boost::nowide::ifstream infile(path);
if (!infile.good()) {
throw Slic3r::RuntimeError(std::string("Load step file failed.\nCannot open file for reading.\n"));
return false;
}
boost::filesystem::path temp_path(temporary_dir());
std::string temp_step_path = temp_path.string() + "/temp.step";
boost::nowide::remove(temp_step_path.c_str());
boost::nowide::ofstream temp_file(temp_step_path, std::ios::app);
std::string temp_line;
while (std::getline(infile, temp_line)) {
if (m_encode_type == EncodedType::UTF8) {
//BBS: continue to judge whether is other type
if (isUtf8(temp_line)) {
//BBS: do nothing, but must be checked before checking whether is GBK
}
//BBS: not utf8, then maybe GBK
else if (isGBK(temp_line)) {
m_encode_type = EncodedType::GBK;
}
//BBS: not UTF8 and not GBK, then maybe some kind of special encoded type which we can't handle
// Load the step as UTF and user will see garbage characters in slicer but we have no solution at the moment
else {
m_encode_type = EncodedType::OTHER;
}
}
if (m_encode_type == EncodedType::GBK)
//BBS: transform to UTF8 format if is GBK
//todo: use gbkToUtf8 function to replace
temp_file << decode_path(temp_line.c_str()) << std::endl;
else
temp_file << temp_line.c_str() << std::endl;
}
temp_file.close();
infile.close();
if (m_encode_type == EncodedType::GBK) {
output_path = temp_step_path;
} else {
boost::nowide::remove(temp_step_path.c_str());
output_path = std::string(path);
}
return true;
}
bool StepPreProcessor::isUtf8File(const char* path)
{
boost::nowide::ifstream infile(path);
if (!infile.good()) {
throw Slic3r::RuntimeError(std::string("Load step file failed.\nCannot open file for reading.\n"));
return false;
}
std::string temp_line;
while (std::getline(infile, temp_line)) {
if (!isUtf8(temp_line)) {
infile.close();
return false;
}
}
infile.close();
return true;
}
bool StepPreProcessor::isUtf8(const std::string str)
{
size_t num = 0;
size_t i = 0;
while (i < str.length()) {
const unsigned char lead = static_cast<unsigned char>(str[i]);
if ((lead & 0x80) == 0x00) {
i++;
// preNum() counts the leading 1 bits, and a multi-byte sequence is 2 to 4
// bytes long, so anything outside that range is not a lead byte.
} else if ((num = preNum(lead)) >= 2 && num <= 4) {
if (i + num > str.length())
return false;
i++;
for (size_t j = 0; j < num - 1; j++) {
if ((static_cast<unsigned char>(str[i]) & 0xc0) != 0x80)
return false;
i++;
}
} else {
return false;
}
}
return true;
}
bool StepPreProcessor::isGBK(const std::string str) {
size_t i = 0;
while (i < str.length()) {
// char is signed here, so every byte compares <= 0x7f unless widened first.
const unsigned char lead = static_cast<unsigned char>(str[i]);
if (lead <= 0x7f) {
i++;
continue;
} else {
if (i + 1 >= str.length())
return false;
const unsigned char trail = static_cast<unsigned char>(str[i + 1]);
if (lead >= 0x81 &&
lead <= 0xfe &&
trail >= 0x40 &&
trail <= 0xfe &&
trail != 0xf7) {
i += 2;
continue;
}
else {
return false;
}
}
}
return true;
}
int StepPreProcessor::preNum(const unsigned char byte) {
unsigned char mask = 0x80;
int num = 0;
for (int i = 0; i < 8; i++) {
if ((byte & mask) == mask) {
mask = mask >> 1;
num++;
} else {
break;
}
}
return num;
}
static void getNamedSolids(const TopLoc_Location& location,
const std::string& prefix,
unsigned int& id,
const Handle(XCAFDoc_ShapeTool) shapeTool,
const TDF_Label label,
std::vector<NamedSolid>& namedSolids,
bool isSplitCompound = false) {
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) ||
label.FindAttribute(TDataStd_Name::GetID(), shapeName))
name = TCollection_AsciiString(shapeName->Get()).ToCString();
if (name == "" || !StepPreProcessor::isUtf8(name))
name = std::to_string(id++);
std::string fullName{name};
TopLoc_Location localLocation = location * shapeTool->GetLocation(label);
NCollection_Sequence<TDF_Label> components;
if (shapeTool->GetComponents(referredLabel, components)) {
for (int compIndex = 1; compIndex <= components.Length(); ++compIndex) {
getNamedSolids(localLocation, fullName, id, shapeTool, components.Value(compIndex), namedSolids, isSplitCompound);
}
} else {
TopoDS_Shape shape;
TopExp_Explorer explorer;
shapeTool->GetShape(referredLabel, shape);
TopAbs_ShapeEnum shape_type = shape.ShapeType();
BRepBuilderAPI_Transform transform(shape, localLocation, true);
int i = 0;
switch (shape_type) {
case TopAbs_COMPOUND:
case TopAbs_COMPSOLID:
if (!isSplitCompound) {
namedSolids.emplace_back(transform.Shape(), fullName);
} else {
for (explorer.Init(transform.Shape(), TopAbs_SOLID); explorer.More(); explorer.Next()) {
i++;
const TopoDS_Shape& currentShape = explorer.Current();
namedSolids.emplace_back(TopoDS::Solid(currentShape), fullName + "-SOLID-" + std::to_string(i));
}
// Surface models have no solids: each shell outside a solid is a part, and loose faces stay together.
i = 0;
for (explorer.Init(transform.Shape(), TopAbs_SHELL, TopAbs_SOLID); explorer.More(); explorer.Next())
namedSolids.emplace_back(explorer.Current(), fullName + "-SHELL-" + std::to_string(++i));
TopoDS_Compound loose_faces;
TopoDS_Builder builder;
builder.MakeCompound(loose_faces);
bool has_loose_faces = false;
for (explorer.Init(transform.Shape(), TopAbs_FACE, TopAbs_SHELL); explorer.More(); explorer.Next()) {
builder.Add(loose_faces, explorer.Current());
has_loose_faces = true;
}
if (has_loose_faces)
namedSolids.emplace_back(loose_faces, fullName);
}
break;
case TopAbs_SOLID:
namedSolids.emplace_back(TopoDS::Solid(transform.Shape()), fullName);
break;
case TopAbs_SHELL:
namedSolids.emplace_back(TopoDS::Shell(transform.Shape()), fullName);
break;
default:
break;
}
}
}
//bool load_step(const char *path, Model *model, bool& is_cancel,
// double linear_deflection/*=0.003*/,
// double angle_deflection/*= 0.5*/,
// bool isSplitCompound,
// ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn, long& mesh_face_num)
//{
// bool cb_cancel = false;
// if (stepFn) {
// stepFn(LOAD_STEP_STAGE_READ_FILE, 0, 1, cb_cancel);
// is_cancel = cb_cancel;
// if (cb_cancel) {
// return false;
// }
// }
//
// if (!StepPreProcessor::isUtf8File(path) && isUtf8Fn)
// isUtf8Fn(false);
// std::string file_after_preprocess = std::string(path);
//
// std::vector<NamedSolid> namedSolids;
// Handle(TDocStd_Document) document;
// Handle(XCAFApp_Application) application = XCAFApp_Application::GetApplication();
// application->NewDocument(file_after_preprocess.c_str(), 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(file_after_preprocess.c_str());
// if (stat != IFSelect_RetDone || !reader.Transfer(document)) {
// application->Close(document);
// throw std::logic_error{ std::string{"Could not read '"} + path + "'" };
// return false;
// }
// Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(document->Main());
// TDF_LabelSequence topLevelShapes;
// shapeTool->GetFreeShapes(topLevelShapes);
//
// unsigned int id{1};
// Standard_Integer topShapeLength = topLevelShapes.Length() + 1;
// auto stage_unit2 = topShapeLength / LOAD_STEP_STAGE_UNIT_NUM + 1;
//
// for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) {
// if (stepFn) {
// if ((iLabel % stage_unit2) == 0) {
// stepFn(LOAD_STEP_STAGE_GET_SOLID, iLabel, topShapeLength, cb_cancel);
// is_cancel = cb_cancel;
// }
// if (cb_cancel) {
// shapeTool.reset(nullptr);
// application->Close(document);
// return false;
// }
// }
// getNamedSolids(TopLoc_Location{}, "", id, shapeTool, topLevelShapes.Value(iLabel), namedSolids, isSplitCompound);
// }
//
// std::vector<stl_file> stl;
// stl.resize(namedSolids.size());
// tbb::parallel_for(tbb::blocked_range<size_t>(0, namedSolids.size()), [&](const tbb::blocked_range<size_t> &range) {
// for (size_t i = range.begin(); i < range.end(); i++) {
// BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_deflection, false, angle_deflection, true);
// // BBS: calculate total number of the nodes and triangles
// int aNbNodes = 0;
// int aNbTriangles = 0;
// for (TopExp_Explorer anExpSF(namedSolids[i].solid, 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();
// }
// }
//
// if (aNbTriangles == 0 || aNbNodes == 0)
// // BBS: No triangulation on the shape.
// continue;
//
// stl[i].stats.type = inmemory;
// stl[i].stats.number_of_facets = (uint32_t) aNbTriangles;
// stl[i].stats.original_num_facets = stl[i].stats.number_of_facets;
// stl_allocate(&stl[i]);
//
// std::vector<Vec3f> 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(namedSolids[i].solid, 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();
// Standard_Integer anId[3] = {};
// for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) {
// Poly_Triangle aTri = aTriangulation->Triangle(aTriIter);
//
// aTri.Get(anId[0], anId[1], anId[2]);
// if (anOrientation == TopAbs_REVERSED)
// std::swap(anId[1], anId[2]);
// // BBS: save triangles facets
// stl_facet facet;
// facet.vertex[0] = points[anId[0] + aNodeOffset - 1].cast<float>();
// facet.vertex[1] = points[anId[1] + aNodeOffset - 1].cast<float>();
// facet.vertex[2] = points[anId[2] + aNodeOffset - 1].cast<float>();
// facet.extra[0] = 0;
// facet.extra[1] = 0;
// stl_normal normal;
// stl_calculate_normal(normal, &facet);
// stl_normalize_vector(normal);
// facet.normal = normal;
// stl[i].facet_start[aTriangleOffet + aTriIter - 1] = facet;
// }
//
// aNodeOffset += aTriangulation->NbNodes();
// aTriangleOffet += aTriangulation->NbTriangles();
// }
// }
// });
//
// if (mesh_face_num != -1) {
// for (size_t i = 0; i < stl.size(); i++) {
// // Test for overflow
// mesh_face_num += stl[i].stats.number_of_facets;
// }
// return true;
// }
//
// ModelObject *new_object = model->add_object();
// const char * last_slash = strrchr(path, DIR_SEPARATOR);
// new_object->name.assign((last_slash == nullptr) ? path : last_slash + 1);
// new_object->input_file = path;
//
// auto stage_unit3 = stl.size() / LOAD_STEP_STAGE_UNIT_NUM + 1;
// for (size_t i = 0; i < stl.size(); i++) {
// if (stepFn) {
// if ((i % stage_unit3) == 0) {
// stepFn(LOAD_STEP_STAGE_GET_MESH, i, stl.size(), cb_cancel);
// is_cancel = cb_cancel;
// }
// if (cb_cancel) {
// model->delete_object(new_object);
// shapeTool.reset(nullptr);
// application->Close(document);
// return false;
// }
// }
//
// //BBS: maybe mesh is empty from step file. Don't add
// if (stl[i].stats.number_of_facets > 0) {
// TriangleMesh triangle_mesh;
// triangle_mesh.from_stl(stl[i]);
// ModelVolume* new_volume = new_object->add_volume(std::move(triangle_mesh));
// new_volume->name = namedSolids[i].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;
// }
// }
//
// shapeTool.reset(nullptr);
// application->Close(document);
//
// //BBS: no valid shape from the step, delete the new object as well
// if (new_object->volumes.size() == 0) {
// model->delete_object(new_object);
// return false;
// }
//
// return true;
//}
Step::Step(fs::path path, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn):
m_stepFn(stepFn),
m_utf8Fn(isUtf8Fn)
{
m_path = path.string();
m_app->NewDocument(TCollection_ExtendedString("BinXCAF"), m_doc);
}
Step::Step(std::string path, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn) :
m_path(path),
m_stepFn(stepFn),
m_utf8Fn(isUtf8Fn)
{
m_app->NewDocument(TCollection_ExtendedString("BinXCAF"), m_doc);
}
Step::~Step()
{
m_app->Close(m_doc);
}
void Step::update_process(int load_stage, int current, int total, bool& cancel)
{
if (m_stepFn) {
m_stepFn(load_stage, current, total, cancel);
}
}
Step::Step_Status Step::load()
{
if (!StepPreProcessor::isUtf8File(m_path.c_str()) && m_utf8Fn) {
m_utf8Fn(false);
return Step_Status::LOAD_ERROR;
}
std::atomic<bool> stop_load_flag = false;
Handle(StepProgressIncdicator) incdicator = new StepProgressIncdicator(stop_load_flag);
bool task_result = false;
bool cb_cancel = false;
int progress = 0;
bool load_result = false;
auto task = new boost::thread(Slic3r::create_thread([&]() -> void {
STEPCAFControl_Reader reader;
reader.SetNameMode(true);
IFSelect_ReturnStatus stat = reader.ReadFile(m_path.c_str());
if (cb_cancel) return;
progress = 3;
if (stat != IFSelect_RetDone || !reader.Transfer(m_doc, incdicator->Start())) {
load_result = false;
task_result = true;
return;
}
if (cb_cancel) return;
progress = 6;
m_shape_tool = XCAFDoc_DocumentTool::ShapeTool(m_doc->Main());
NCollection_Sequence<TDF_Label> topLevelShapes;
m_shape_tool->GetFreeShapes(topLevelShapes);
unsigned int id{ 1 };
int topShapeLength = topLevelShapes.Length() + 1;
for (int iLabel = 1; iLabel < topShapeLength; ++iLabel) {
if (cb_cancel) return;
getNamedSolids(TopLoc_Location{}, "", id, m_shape_tool, topLevelShapes.Value(iLabel), m_name_solids);
}
progress = 10;
load_result = true;
task_result = true;
}));
while (!task_result) {
boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
update_process(LOAD_STEP_STAGE_READ_FILE, progress, 10, cb_cancel);
if (cb_cancel) {
stop_load_flag.store(true);
if (task) {
if (task->joinable()) {
task->join();
delete task;
}
}
return Step_Status::CANCEL;
}
}
if (task){
if (task->joinable()) {
task->join();
delete task;
}
}
if (load_result) {
return Step_Status::LOAD_SUCCESS;
}else {
return Step_Status::LOAD_ERROR;
}
}
Step::Step_Status Step::mesh(Model* model,
bool& is_cancel,
bool isSplitCompound,
double linear_deflection/*=0.003*/,
double angle_deflection/*= 0.5*/)
{
bool task_result = false;
bool cb_cancel = false;
float progress = .0;
std::atomic<int> meshed_solid_num = 0;
std::vector<NamedSolid> namedSolids;
float progress_2 = .0;
ModelObject* new_object = model->add_object();
const char* last_slash = strrchr(m_path.c_str(), DIR_SEPARATOR);
new_object->name.assign((last_slash == nullptr) ? m_path.c_str() : last_slash + 1);
new_object->input_file = m_path.c_str();
auto task = new boost::thread(Slic3r::create_thread([&]() -> void {
NCollection_Sequence<TDF_Label> topLevelShapes;
m_shape_tool->GetFreeShapes(topLevelShapes);
unsigned int id{ 1 };
int topShapeLength = topLevelShapes.Length() + 1;
for (int iLabel = 1; iLabel < topShapeLength; ++iLabel) {
progress = static_cast<double>(iLabel) / (topShapeLength-1);
if (cb_cancel) {
return;
}
getNamedSolids(TopLoc_Location{}, "", id, m_shape_tool, topLevelShapes.Value(iLabel), namedSolids, isSplitCompound);
}
std::vector<stl_file> stl;
stl.resize(namedSolids.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, namedSolids.size()), [&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); i++) {
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_deflection, false, angle_deflection, true);
// BBS: calculate total number of the nodes and triangles
int aNbNodes = 0;
int aNbTriangles = 0;
for (TopExp_Explorer anExpSF(namedSolids[i].solid, 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();
}
}
if (aNbTriangles == 0 || aNbNodes == 0)
// BBS: No triangulation on the shape.
continue;
stl[i].stats.type = inmemory;
stl[i].stats.number_of_facets = (uint32_t)aNbTriangles;
stl[i].stats.original_num_facets = stl[i].stats.number_of_facets;
stl_allocate(&stl[i]);
std::vector<Vec3f> points;
points.reserve(aNbNodes);
// BBS: count faces missing triangulation
int aNbFacesNoTri = 0;
// BBS: fill temporary triangulation
int aNodeOffset = 0;
int aTriangleOffet = 0;
for (TopExp_Explorer anExpSF(namedSolids[i].solid, 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 (int aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
aPnt.Transform(aTrsf);
points.emplace_back(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()));
}
// BBS: copy triangles
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
int anId[3] = {};
for (int aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) {
Poly_Triangle aTri = aTriangulation->Triangle(aTriIter);
aTri.Get(anId[0], anId[1], anId[2]);
if (anOrientation == TopAbs_REVERSED)
std::swap(anId[1], anId[2]);
// BBS: save triangles facets
stl_facet facet;
facet.vertex[0] = points[anId[0] + aNodeOffset - 1].cast<float>();
facet.vertex[1] = points[anId[1] + aNodeOffset - 1].cast<float>();
facet.vertex[2] = points[anId[2] + aNodeOffset - 1].cast<float>();
facet.extra[0] = 0;
facet.extra[1] = 0;
stl_normal normal;
stl_calculate_normal(normal, &facet);
stl_normalize_vector(normal);
facet.normal = normal;
stl[i].facet_start[aTriangleOffet + aTriIter - 1] = facet;
}
aNodeOffset += aTriangulation->NbNodes();
aTriangleOffet += aTriangulation->NbTriangles();
}
meshed_solid_num.fetch_add(1, std::memory_order_relaxed);
}
});
for (size_t i = 0; i < stl.size(); i++) {
progress_2 = static_cast<float>(i) / stl.size();
if (cb_cancel)
return;
//BBS: maybe mesh is empty from step file. Don't add
if (stl[i].stats.number_of_facets > 0) {
TriangleMesh triangle_mesh;
triangle_mesh.from_stl(stl[i]);
ModelVolume* new_volume = new_object->add_volume(std::move(triangle_mesh));
new_volume->name = namedSolids[i].name;
new_volume->source.input_file = m_path.c_str();
new_volume->source.object_idx = (int)model->objects.size() - 1;
new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
}
}
task_result = true;
}));
while (!task_result) {
boost::this_thread::sleep_for(boost::chrono::milliseconds(300));
if (progress_2 > 0) {
// third progress
update_process(LOAD_STEP_STAGE_GET_MESH, static_cast<int>(progress_2 * 100), 100, cb_cancel);
}else {
if (meshed_solid_num.load()) {
// second progress
int meshed_solid = meshed_solid_num.load();
update_process(LOAD_STEP_STAGE_GET_SOLID, static_cast<int>((float)meshed_solid / namedSolids.size() * 10) + 10, 20, cb_cancel);
} else {
if (progress > 0) {
// first progress
update_process(LOAD_STEP_STAGE_GET_SOLID, static_cast<int>(progress * 10), 20, cb_cancel);
}
}
}
if (cb_cancel) {
if (task) {
if (task->joinable()) {
task->join();
delete task;
}
}
return Step_Status::CANCEL;
}
}
if (task) {
if (task->joinable()) {
task->join();
delete task;
}
}
//BBS: no valid shape from the step, delete the new object as well
if (new_object->volumes.size() == 0) {
model->delete_object(new_object);
return Step_Status::MESH_ERROR;
}
return Step_Status::MESH_SUCCESS;
}
void Step::clean_mesh_data()
{
for (const auto& name_solid : m_name_solids) {
BRepTools::Clean(name_solid.solid);
}
}
unsigned int Step::get_triangle_num(double linear_deflection, double angle_deflection)
{
unsigned int tri_num = 0;
try {
Handle(StepProgressIncdicator) progress = new StepProgressIncdicator(m_stop_mesh);
clean_mesh_data();
IMeshTools_Parameters param;
param.Deflection = linear_deflection;
param.Angle = angle_deflection;
param.InParallel = true;
for (int i = 0; i < m_name_solids.size(); ++i) {
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, param, progress->Start());
for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
TopLoc_Location aLoc;
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc);
if (!aTriangulation.IsNull()) {
tri_num += aTriangulation->NbTriangles();
}
}
if (m_stop_mesh.load()) {
return 0;
}
}
} catch(const Exception &) {
return 0;
}
return tri_num;
}
unsigned int Step::get_triangle_num_tbb(double linear_deflection, double angle_deflection)
{
unsigned int tri_num = 0;
clean_mesh_data();
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_name_solids.size()),
[&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); i++) {
unsigned int solids_tri_num = 0;
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, linear_deflection, false, angle_deflection, true);
for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
TopLoc_Location aLoc;
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc);
if (!aTriangulation.IsNull()) {
solids_tri_num += aTriangulation->NbTriangles();
}
}
m_name_solids[i].tri_face_cout = solids_tri_num;
}
});
for (int i = 0; i < m_name_solids.size(); ++i) {
tri_num += m_name_solids[i].tri_face_cout;
}
return tri_num;
}
}; // namespace Slic3r