Port STEP import dialog from BambuStudio (#9102)

![image](https://github.com/user-attachments/assets/db27ee5d-c158-4729-a648-a33f1222a479)


Thanks BambuLab!

Fix #8820
This commit is contained in:
SoftFever
2025-04-05 21:20:51 +08:00
committed by GitHub
19 changed files with 817 additions and 74 deletions

View File

@@ -411,6 +411,17 @@ void AppConfig::set_defaults()
set_str("print", "timelapse", "1");
}
if (get("enable_step_mesh_setting").empty()) {
set_bool("enable_step_mesh_setting", true);
}
if (get("linear_defletion", "angle_defletion").empty()) {
set("linear_defletion", "0.003");
set("angle_defletion", "0.5");
}
if (get("is_split_compound").empty()) {
set_bool("is_split_compound", false);
}
// Remove legacy window positions/sizes
erase("app", "main_frame_maximized");
erase("app", "main_frame_pos");

View File

@@ -33,9 +33,8 @@
#include "TopExp_Explorer.hxx"
#include "TopExp_Explorer.hxx"
#include "BRep_Tool.hxx"
const double STEP_TRANS_CHORD_ERROR = 0.003;
const double STEP_TRANS_ANGLE_RES = 0.5;
#include "BRepTools.hxx"
#include <IMeshTools_Parameters.hxx>
namespace Slic3r {
@@ -166,26 +165,24 @@ int StepPreProcessor::preNum(const unsigned char byte) {
return num;
}
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 std::string& prefix,
unsigned int& id, const Handle(XCAFDoc_ShapeTool) shapeTool,
const TDF_Label label, std::vector<NamedSolid>& namedSolids) {
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))
if (referredLabel.FindAttribute(TDataStd_Name::GetID(), shapeName) ||
label.FindAttribute(TDataStd_Name::GetID(), shapeName))
name = TCollection_AsciiString(shapeName->Get()).ToCString();
if (name == "")
if (name == "" || !StepPreProcessor::isUtf8(name))
name = std::to_string(id++);
std::string fullName{name};
@@ -193,7 +190,7 @@ static void getNamedSolids(const TopLoc_Location& location, const std::string& p
TDF_LabelSequence components;
if (shapeTool->GetComponents(referredLabel, components)) {
for (Standard_Integer compIndex = 1; compIndex <= components.Length(); ++compIndex) {
getNamedSolids(localLocation, fullName, id, shapeTool, components.Value(compIndex), namedSolids);
getNamedSolids(localLocation, fullName, id, shapeTool, components.Value(compIndex), namedSolids, isSplitCompound);
}
} else {
TopoDS_Shape shape;
@@ -204,12 +201,20 @@ static void getNamedSolids(const TopLoc_Location& location, const std::string& p
int i = 0;
switch (shape_type) {
case TopAbs_COMPOUND:
if (!isSplitCompound) {
namedSolids.emplace_back(TopoDS::Compound(transform.Shape()), fullName);
break;
}
case TopAbs_COMPSOLID:
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));
}
if (!isSplitCompound) {
namedSolids.emplace_back(TopoDS::CompSolid(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));
}
}
break;
case TopAbs_SOLID:
namedSolids.emplace_back(TopoDS::Solid(transform.Shape()), fullName);
@@ -223,7 +228,11 @@ static void getNamedSolids(const TopLoc_Location& location, const std::string& p
}
}
bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn)
bool load_step(const char *path, Model *model, bool& is_cancel,
double linear_defletion/*=0.003*/,
double angle_defletion/*= 0.5*/,
bool isSplitCompound,
ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn, long& mesh_face_num)
{
bool cb_cancel = false;
if (stepFn) {
@@ -271,14 +280,14 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre
return false;
}
}
getNamedSolids(TopLoc_Location{}, "", id, shapeTool, topLevelShapes.Value(iLabel), namedSolids);
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, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true);
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true);
// BBS: calculate total number of the nodes and triangles
int aNbNodes = 0;
int aNbTriangles = 0;
@@ -324,7 +333,7 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre
}
// BBS: copy triangles
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
Standard_Integer anId[3];
Standard_Integer anId[3] = {};
for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) {
Poly_Triangle aTri = aTriangulation->Triangle(aTriIter);
@@ -351,6 +360,14 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre
}
});
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);
@@ -395,4 +412,109 @@ bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgre
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);
}
bool Step::load()
{
if (!StepPreProcessor::isUtf8File(m_path.c_str()) && m_utf8Fn) {
m_utf8Fn(false);
return false;
}
STEPCAFControl_Reader reader;
reader.SetNameMode(true);
IFSelect_ReturnStatus stat = reader.ReadFile(m_path.c_str());
if (stat != IFSelect_RetDone || !reader.Transfer(m_doc)) {
m_app->Close(m_doc);
return false;
}
m_shape_tool = XCAFDoc_DocumentTool::ShapeTool(m_doc->Main());
TDF_LabelSequence topLevelShapes;
m_shape_tool->GetFreeShapes(topLevelShapes);
unsigned int id{ 1 };
Standard_Integer topShapeLength = topLevelShapes.Length() + 1;
for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) {
getNamedSolids(TopLoc_Location{}, "", id, m_shape_tool, topLevelShapes.Value(iLabel), m_name_solids);
}
return true;
}
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_defletion, double angle_defletion)
{
unsigned int tri_num = 0;
try {
Handle(StepProgressIncdicator) progress = new StepProgressIncdicator(m_stop_mesh);
clean_mesh_data();
IMeshTools_Parameters param;
param.Deflection = linear_defletion;
param.Angle = angle_defletion;
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(Exception e) {
return 0;
}
return tri_num;
}
unsigned int Step::get_triangle_num_tbb(double linear_defletion, double angle_defletion)
{
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_defletion, false, angle_defletion, 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

View File

@@ -1,5 +1,14 @@
#ifndef slic3r_Format_STEP_hpp_
#define slic3r_Format_STEP_hpp_
#include "XCAFDoc_DocumentTool.hxx"
#include "XCAFApp_Application.hxx"
#include "XCAFDoc_ShapeTool.hxx"
#include <boost/filesystem/path.hpp>
#include <boost/filesystem.hpp>
#include <Message_ProgressIndicator.hxx>
#include <atomic>
namespace fs = boost::filesystem;
namespace Slic3r {
@@ -16,8 +25,25 @@ const int LOAD_STEP_STAGE_UNIT_NUM = 5;
typedef std::function<void(int load_stage, int current, int total, bool& cancel)> ImportStepProgressFn;
typedef std::function<void(bool isUtf8)> StepIsUtf8Fn;
struct NamedSolid
{
NamedSolid(const TopoDS_Shape& s,
const std::string& n) : solid{ s }, name{ n } {
}
const TopoDS_Shape solid;
const std::string name;
int tri_face_cout = 0;
};
//BBS: Load an step file into a provided model.
extern bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn proFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
extern bool load_step(const char *path, Model *model,
bool& is_cancel,
double linear_defletion = 0.003,
double angle_defletion = 0.5,
bool isSplitCompound = false,
ImportStepProgressFn proFn = nullptr,
StepIsUtf8Fn isUtf8Fn = nullptr,
long& mesh_face_num = *(new long(-1)));
//BBS: Used to detect what kind of encoded type is used in name field of step
// If is encoded in UTF8, the file don't need to be handled, then return the original path directly.
@@ -36,14 +62,49 @@ class StepPreProcessor {
public:
bool preprocess(const char* path, std::string &output_path);
static bool isUtf8File(const char* path);
private:
static bool isUtf8(const std::string str);
private:
static bool isGBK(const std::string str);
static int preNum(const unsigned char byte);
//BBS: default is UTF8 for most step file.
EncodedType m_encode_type = EncodedType::UTF8;
};
class StepProgressIncdicator : public Message_ProgressIndicator
{
public:
StepProgressIncdicator(std::atomic<bool>& stop_flag) : should_stop(stop_flag){}
Standard_Boolean UserBreak() override { return should_stop.load(); }
void Show(const Message_ProgressScope&, const Standard_Boolean) override {
std::cout << "Progress: " << GetPosition() << "%" << std::endl;
}
private:
std::atomic<bool>& should_stop;
};
class Step
{
public:
Step(fs::path path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
Step(std::string path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
bool load();
unsigned int get_triangle_num(double linear_defletion, double angle_defletion);
unsigned int get_triangle_num_tbb(double linear_defletion, double angle_defletion);
void clean_mesh_data();
std::atomic<bool> m_stop_mesh;
private:
std::string m_path;
ImportStepProgressFn m_stepFn;
StepIsUtf8Fn m_utf8Fn;
Handle(XCAFApp_Application) m_app = XCAFApp_Application::GetApplication();
Handle(TDocStd_Document) m_doc;
Handle(XCAFDoc_ShapeTool) m_shape_tool;
std::vector<NamedSolid> m_name_solids;
};
}; // namespace Slic3r
#endif /* slic3r_Format_STEP_hpp_ */

View File

@@ -4,6 +4,7 @@
#include "../libslic3r.h"
#include <map>
#include <string>
#include <cfloat>
namespace Slic3r {

View File

@@ -27,6 +27,7 @@
#include "SVG.hpp"
#include <Eigen/Dense>
#include <functional>
#include "GCodeWriter.hpp"
// BBS: for segment
@@ -176,17 +177,68 @@ Model::~Model()
Slic3r::remove_backup(*this, true);
}
Model Model::read_from_step(const std::string& input_file,
LoadStrategy options,
ImportStepProgressFn stepFn,
StepIsUtf8Fn stepIsUtf8Fn,
std::function<int(Slic3r::Step&, double&, double&, bool&)> step_mesh_fn,
double linear_defletion,
double angle_defletion,
bool is_split_compound)
{
Model model;
bool result = false;
bool is_cb_cancel = false;
std::string message;
Step step_file(input_file);
step_file.load();
if (step_mesh_fn) {
if (step_mesh_fn(step_file, linear_defletion, angle_defletion, is_split_compound) == -1) {
Model empty_model;
return empty_model;
}
}
result = load_step(input_file.c_str(), &model, is_cb_cancel, linear_defletion, angle_defletion, is_split_compound, stepFn, stepIsUtf8Fn);
if (is_cb_cancel) {
Model empty_model;
return empty_model;
}
if (!result) {
if (message.empty())
throw Slic3r::RuntimeError(_L("Loading of a model file failed."));
else
throw Slic3r::RuntimeError(message);
}
if (model.objects.empty())
throw Slic3r::RuntimeError(_L("The supplied file couldn't be read because it's empty"));
for (ModelObject *o : model.objects)
o->input_file = input_file;
if (options & LoadStrategy::AddDefaultInstances)
model.add_default_instances();
return model;
}
// BBS: add part plate related logic
// BBS: backup & restore
// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions,
LoadStrategy options, PlateDataPtrs* plate_data, std::vector<Preset*>* project_presets, bool *is_xxx, Semver* file_version, Import3mfProgressFn proFn,
ImportstlProgressFn stlFn,
ImportStepProgressFn stepFn,
StepIsUtf8Fn stepIsUtf8Fn,
BBLProject * project,
int plate_id,
ObjImportColorFn objFn)
Model Model::read_from_file(const std::string& input_file,
DynamicPrintConfig* config,
ConfigSubstitutionContext* config_substitutions,
LoadStrategy options,
PlateDataPtrs* plate_data,
std::vector<Preset*>* project_presets,
bool *is_xxx,
Semver* file_version,
Import3mfProgressFn proFn,
ImportstlProgressFn stlFn,
BBLProject * project,
int plate_id,
ObjImportColorFn objFn)
{
Model model;
@@ -210,10 +262,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
bool result = false;
bool is_cb_cancel = false;
std::string message;
if (boost::algorithm::iends_with(input_file, ".stp") ||
boost::algorithm::iends_with(input_file, ".step"))
result = load_step(input_file.c_str(), &model, is_cb_cancel, stepFn, stepIsUtf8Fn);
else if (boost::algorithm::iends_with(input_file, ".stl"))
if (boost::algorithm::iends_with(input_file, ".stl"))
result = load_stl(input_file.c_str(), &model, nullptr, stlFn);
else if (boost::algorithm::iends_with(input_file, ".oltp"))
result = load_stl(input_file.c_str(), &model, nullptr, stlFn,256);

View File

@@ -6,6 +6,7 @@
#include "Geometry.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
#include "AppConfig.hpp"
#include "PrintConfig.hpp"
#include "Slicing.hpp"
#include "SLA/SupportPoint.hpp"
@@ -1540,6 +1541,15 @@ public:
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
static Model read_from_step(const std::string& input_file,
LoadStrategy options,
ImportStepProgressFn stepFn,
StepIsUtf8Fn stepIsUtf8Fn,
std::function<int(Slic3r::Step&, double&, double&, bool&)> step_mesh_fn,
double linear_defletion,
double angle_defletion,
bool is_split_compound);
//BBS: add part plate related logic
// BBS: backup
//BBS: is_xxx is used for is_bbs_3mf when loading 3mf, is used for is_inches when loading amf
@@ -1549,8 +1559,6 @@ public:
LoadStrategy options = LoadStrategy::AddDefaultInstances, PlateDataPtrs* plate_data = nullptr,
std::vector<Preset*>* project_presets = nullptr, bool* is_xxx = nullptr, Semver* file_version = nullptr, Import3mfProgressFn proFn = nullptr,
ImportstlProgressFn stlFn = nullptr,
ImportStepProgressFn stepFn = nullptr,
StepIsUtf8Fn stepIsUtf8Fn = nullptr,
BBLProject * project = nullptr,
int plate_id = 0,
ObjImportColorFn objFn = nullptr

View File

@@ -87,6 +87,7 @@
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/filesystem.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/regex.hpp>
// boost/property_tree/json_parser/detail/parser.hpp includes boost/bind.hpp, which is deprecated.
// Suppress the following boost message: