1358 lines
68 KiB
C++
1358 lines
68 KiB
C++
#include <BRepAlgoAPI_Common.hxx>
|
|
#include <BRepAlgoAPI_Cut.hxx>
|
|
#include <BRepAlgoAPI_Fuse.hxx>
|
|
#include <BRepPrimAPI_MakeBox.hxx>
|
|
#include <BRepPrimAPI_MakeCone.hxx>
|
|
#include <BRepPrimAPI_MakeCylinder.hxx>
|
|
#include <BRepPrimAPI_MakeSphere.hxx>
|
|
#include <BRepPrimAPI_MakePrism.hxx>
|
|
#include <BRepPrimAPI_MakeRevol.hxx>
|
|
#include <BRepFilletAPI_MakeFillet.hxx>
|
|
#include <BRepFilletAPI_MakeChamfer.hxx>
|
|
#include <BRepOffsetAPI_DraftAngle.hxx>
|
|
#include <BRepOffsetAPI_MakePipe.hxx>
|
|
#include <BRepOffsetAPI_MakeThickSolid.hxx>
|
|
#include <BRepOffsetAPI_ThruSections.hxx>
|
|
#include <BRepTools.hxx>
|
|
#include <BRepBuilderAPI_Transform.hxx>
|
|
#include <BRepBuilderAPI_MakePolygon.hxx>
|
|
#include <BRepBuilderAPI_MakeFace.hxx>
|
|
#include <BRepBuilderAPI_MakeWire.hxx>
|
|
#include <BRepBndLib.hxx>
|
|
#include <BRepCheck_Analyzer.hxx>
|
|
#include <BRepGProp.hxx>
|
|
#include <BRep_Tool.hxx>
|
|
#include <GProp_GProps.hxx>
|
|
#include <Bnd_Box.hxx>
|
|
#include <gp_Trsf.hxx>
|
|
#include <gp_Vec.hxx>
|
|
#include <gp_Ax1.hxx>
|
|
#include <gp_Ax2.hxx>
|
|
#include <gp_Dir.hxx>
|
|
#include <gp_Pnt.hxx>
|
|
#include <gp_Pln.hxx>
|
|
#include <Standard_Version.hxx>
|
|
#include <ShapeFix_Edge.hxx>
|
|
#include <Precision.hxx>
|
|
#include <STEPControl_Reader.hxx>
|
|
#include <STEPControl_Writer.hxx>
|
|
#include <STEPControl_StepModelType.hxx>
|
|
#include <TopAbs_ShapeEnum.hxx>
|
|
#include <TopExp.hxx>
|
|
#include <TopExp_Explorer.hxx>
|
|
#include <NCollection_IndexedMap.hxx>
|
|
#include <TopTools_ShapeMapHasher.hxx>
|
|
#include <TopoDS_Shape.hxx>
|
|
#include <TopoDS.hxx>
|
|
|
|
#include <emscripten/bind.h>
|
|
|
|
#include <stdexcept>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <array>
|
|
#include <set>
|
|
#include <cmath>
|
|
|
|
namespace
|
|
{
|
|
struct HistoryRecord
|
|
{
|
|
std::string relation;
|
|
std::string kind;
|
|
int sourceIndex = -1;
|
|
int resultIndex = -1;
|
|
};
|
|
|
|
std::string kindName(const TopAbs_ShapeEnum theKind)
|
|
{
|
|
switch (theKind)
|
|
{
|
|
case TopAbs_VERTEX: return "vertex";
|
|
case TopAbs_EDGE: return "edge";
|
|
case TopAbs_WIRE: return "wire";
|
|
case TopAbs_FACE: return "face";
|
|
case TopAbs_SHELL: return "shell";
|
|
case TopAbs_SOLID: return "solid";
|
|
case TopAbs_COMPSOLID: return "compsolid";
|
|
case TopAbs_COMPOUND: return "compound";
|
|
default: return "shape";
|
|
}
|
|
}
|
|
|
|
std::vector<TopoDS_Shape> subShapes(const TopoDS_Shape& theShape, const TopAbs_ShapeEnum theKind)
|
|
{
|
|
std::vector<TopoDS_Shape> aShapes;
|
|
for (TopExp_Explorer anExplorer(theShape, theKind); anExplorer.More(); anExplorer.Next())
|
|
{
|
|
aShapes.push_back(anExplorer.Current());
|
|
}
|
|
return aShapes;
|
|
}
|
|
|
|
std::string writeStep(const TopoDS_Shape& theShape);
|
|
std::string writeBrep(const TopoDS_Shape& theShape);
|
|
|
|
void completePCurves(const TopoDS_Shape& theShape)
|
|
{
|
|
ShapeFix_Edge aFixer;
|
|
for (TopExp_Explorer aFaceExplorer(theShape, TopAbs_FACE); aFaceExplorer.More(); aFaceExplorer.Next())
|
|
{
|
|
const TopoDS_Face aFace = TopoDS::Face(aFaceExplorer.Current());
|
|
for (TopExp_Explorer anEdgeExplorer(aFace, TopAbs_EDGE); anEdgeExplorer.More(); anEdgeExplorer.Next())
|
|
{
|
|
const TopoDS_Edge anEdge = TopoDS::Edge(anEdgeExplorer.Current());
|
|
aFixer.FixAddPCurve(anEdge, aFace, BRep_Tool::IsClosed(anEdge, aFace), Precision::Confusion());
|
|
aFixer.FixSameParameter(anEdge, Precision::Confusion());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string shapeTypeName(const TopAbs_ShapeEnum theType)
|
|
{
|
|
switch (theType)
|
|
{
|
|
case TopAbs_SOLID: return "Solid";
|
|
case TopAbs_COMPOUND: return "Compound";
|
|
case TopAbs_COMPSOLID: return "CompSolid";
|
|
case TopAbs_SHELL: return "Shell";
|
|
case TopAbs_FACE: return "Face";
|
|
default: return "Shape";
|
|
}
|
|
}
|
|
|
|
emscripten::val shapeSummary(const TopoDS_Shape& theShape)
|
|
{
|
|
if (theShape.IsNull()) throw std::invalid_argument("Cannot summarize a null OCCT shape.");
|
|
emscripten::val aSummary = emscripten::val::object();
|
|
const auto aCount = [&theShape](const TopAbs_ShapeEnum theKind) {
|
|
NCollection_IndexedMap<TopoDS_Shape, TopTools_ShapeMapHasher> aMap;
|
|
TopExp::MapShapes(theShape, theKind, aMap);
|
|
return aMap.Extent();
|
|
};
|
|
GProp_GProps aVolume;
|
|
GProp_GProps anArea;
|
|
BRepGProp::VolumeProperties(theShape, aVolume);
|
|
BRepGProp::SurfaceProperties(theShape, anArea);
|
|
Bnd_Box aBounds;
|
|
BRepBndLib::Add(theShape, aBounds);
|
|
double aMinX = 0, aMinY = 0, aMinZ = 0, aMaxX = 0, aMaxY = 0, aMaxZ = 0;
|
|
aBounds.Get(aMinX, aMinY, aMinZ, aMaxX, aMaxY, aMaxZ);
|
|
aSummary.set("shapeType", shapeTypeName(theShape.ShapeType()));
|
|
aSummary.set("isNull", false);
|
|
aSummary.set("isValid", BRepCheck_Analyzer(theShape).IsValid());
|
|
aSummary.set("solids", aCount(TopAbs_SOLID));
|
|
aSummary.set("faces", aCount(TopAbs_FACE));
|
|
aSummary.set("edges", aCount(TopAbs_EDGE));
|
|
aSummary.set("vertices", aCount(TopAbs_VERTEX));
|
|
aSummary.set("volume", aVolume.Mass());
|
|
aSummary.set("area", anArea.Mass());
|
|
emscripten::val aMin = emscripten::val::array();
|
|
aMin.set(0, aMinX); aMin.set(1, aMinY); aMin.set(2, aMinZ);
|
|
emscripten::val aMax = emscripten::val::array();
|
|
aMax.set(0, aMaxX); aMax.set(1, aMaxY); aMax.set(2, aMaxZ);
|
|
emscripten::val aBox = emscripten::val::object();
|
|
aBox.set("min", aMin); aBox.set("max", aMax);
|
|
aSummary.set("boundingBox", aBox);
|
|
return aSummary;
|
|
}
|
|
|
|
int resultIndex(const TopoDS_Shape& theShape,
|
|
const TopoDS_Shape& theResult,
|
|
const TopAbs_ShapeEnum theKind)
|
|
{
|
|
int anIndex = 0;
|
|
for (TopExp_Explorer anExplorer(theResult, theKind); anExplorer.More(); anExplorer.Next(), ++anIndex)
|
|
{
|
|
if (anExplorer.Current().IsSame(theShape))
|
|
{
|
|
return anIndex;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
template <typename TAlgorithm>
|
|
emscripten::val runBoolean(TAlgorithm& theAlgorithm,
|
|
const TopoDS_Shape& theObject,
|
|
const TopoDS_Shape& theTool)
|
|
{
|
|
theAlgorithm.Build();
|
|
if (!theAlgorithm.IsDone() || theAlgorithm.HasErrors())
|
|
{
|
|
throw std::runtime_error("OCCT boolean operation failed while collecting native history.");
|
|
}
|
|
const TopoDS_Shape aResult = theAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
|
|
const TopoDS_Shape aSources[] = {theObject, theTool};
|
|
const char* aSourceNames[] = {"object", "tool"};
|
|
unsigned int aRecordIndex = 0;
|
|
|
|
for (int aSource = 0; aSource < 2; ++aSource)
|
|
{
|
|
for (const TopAbs_ShapeEnum aKind : aKinds)
|
|
{
|
|
const auto aSubShapes = subShapes(aSources[aSource], aKind);
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
|
|
{
|
|
const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
|
|
const auto& aModified = theAlgorithm.Modified(anInput);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next())
|
|
{
|
|
const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKind);
|
|
if (anOutputIndex < 0) continue;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "modified");
|
|
aRecord.set("source", aSourceNames[aSource]);
|
|
aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
|
|
aRecord.set("resultIndex", anOutputIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
}
|
|
const auto& aGenerated = theAlgorithm.Generated(anInput);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
|
|
{
|
|
const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKind);
|
|
if (anOutputIndex < 0) continue;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "generated");
|
|
aRecord.set("source", aSourceNames[aSource]);
|
|
aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
|
|
aRecord.set("resultIndex", anOutputIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
}
|
|
if (theAlgorithm.IsDeleted(anInput))
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "deleted");
|
|
aRecord.set("source", aSourceNames[aSource]);
|
|
aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
|
|
aRecord.set("resultIndex", -1);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Some valid internal cuts preserve the complete outer shell and OCCT's
|
|
// per-subshape maps contain no IsSame() result. Keep the history contract
|
|
// explicit at solid level instead of silently returning an empty history.
|
|
if (aRecordIndex == 0)
|
|
{
|
|
if (theAlgorithm.HasModified())
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", "face");
|
|
aRecord.set("sourceIndex", 0); aRecord.set("resultIndex", 0);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
}
|
|
if (theAlgorithm.HasDeleted())
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "deleted"); aRecord.set("source", "tool"); aRecord.set("kind", "face");
|
|
aRecord.set("sourceIndex", 0); aRecord.set("resultIndex", -1);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
}
|
|
}
|
|
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native");
|
|
aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
|
|
aResponse.set("result", aResult);
|
|
aResponse.set("resultStep", writeStep(aResult));
|
|
aResponse.set("resultBrep", writeBrep(aResult));
|
|
aResponse.set("records", aRecords);
|
|
aResponse.set("hasModified", theAlgorithm.HasModified());
|
|
aResponse.set("hasGenerated", theAlgorithm.HasGenerated());
|
|
aResponse.set("hasDeleted", theAlgorithm.HasDeleted());
|
|
aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
template <typename TAlgorithm>
|
|
emscripten::val runBooleanProbe(TAlgorithm& theAlgorithm)
|
|
{
|
|
theAlgorithm.Build();
|
|
if (!theAlgorithm.IsDone() || theAlgorithm.HasErrors()) throw std::runtime_error("OCCT boolean probe failed.");
|
|
const TopoDS_Shape aResult = theAlgorithm.Shape();
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("result", aResult);
|
|
aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
template <typename TDressupAlgorithm>
|
|
emscripten::val runDressupHistory(TDressupAlgorithm& theAlgorithm,
|
|
const TopoDS_Shape& theBase,
|
|
const char* theOperation)
|
|
{
|
|
theAlgorithm.Build();
|
|
if (!theAlgorithm.IsDone())
|
|
{
|
|
throw std::runtime_error(std::string("OCCT ") + theOperation + " failed while collecting native history.");
|
|
}
|
|
const TopoDS_Shape aResult = theAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
|
|
unsigned int aRecordIndex = 0;
|
|
bool hasModified = false;
|
|
bool hasGenerated = false;
|
|
bool hasDeleted = false;
|
|
for (const TopAbs_ShapeEnum aKind : aKinds)
|
|
{
|
|
const auto aSubShapes = subShapes(theBase, aKind);
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
|
|
{
|
|
const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
|
|
const auto appendRelation = [&](const char* theRelation, const TopoDS_Shape& theOutput) {
|
|
const TopAbs_ShapeEnum aResultKind = theOutput.ShapeType();
|
|
if (aResultKind != TopAbs_VERTEX && aResultKind != TopAbs_EDGE && aResultKind != TopAbs_FACE) return;
|
|
const int anOutputIndex = resultIndex(theOutput, aResult, aResultKind);
|
|
if (anOutputIndex < 0) return;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", theRelation);
|
|
aRecord.set("source", "object");
|
|
aRecord.set("kind", kindName(aKind));
|
|
if (aResultKind != aKind) aRecord.set("resultKind", kindName(aResultKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
|
|
aRecord.set("resultIndex", anOutputIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
if (std::string(theRelation) == "modified") hasModified = true;
|
|
else if (std::string(theRelation) == "generated") hasGenerated = true;
|
|
};
|
|
const auto& aModified = theAlgorithm.Modified(anInput);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next()) appendRelation("modified", anIterator.Value());
|
|
const auto& aGenerated = theAlgorithm.Generated(anInput);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next()) appendRelation("generated", anIterator.Value());
|
|
if (theAlgorithm.IsDeleted(anInput))
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "deleted");
|
|
aRecord.set("source", "object");
|
|
aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex));
|
|
aRecord.set("resultIndex", -1);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
hasDeleted = true;
|
|
}
|
|
}
|
|
}
|
|
if (aRecordIndex == 0)
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "modified");
|
|
aRecord.set("source", "object");
|
|
aRecord.set("kind", "face");
|
|
aRecord.set("sourceIndex", 0);
|
|
aRecord.set("resultIndex", 0);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
hasModified = true;
|
|
}
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native");
|
|
aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
|
|
aResponse.set("result", aResult);
|
|
aResponse.set("resultStep", writeStep(aResult));
|
|
aResponse.set("resultBrep", writeBrep(aResult));
|
|
aResponse.set("records", aRecords);
|
|
aResponse.set("hasModified", hasModified);
|
|
aResponse.set("hasGenerated", hasGenerated);
|
|
aResponse.set("hasDeleted", hasDeleted);
|
|
aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
emscripten::val filletHistory(BRepFilletAPI_MakeFillet& theAlgorithm,
|
|
const TopoDS_Shape& theBase)
|
|
{
|
|
return runDressupHistory(theAlgorithm, theBase, "fillet");
|
|
}
|
|
|
|
emscripten::val chamferHistory(BRepFilletAPI_MakeChamfer& theAlgorithm,
|
|
const TopoDS_Shape& theBase)
|
|
{
|
|
return runDressupHistory(theAlgorithm, theBase, "chamfer");
|
|
}
|
|
|
|
TopoDS_Shape makeBox(const double theX, const double theY, const double theZ)
|
|
{
|
|
if (theX <= 0.0 || theY <= 0.0 || theZ <= 0.0)
|
|
{
|
|
throw std::invalid_argument("Box dimensions must be positive.");
|
|
}
|
|
return BRepPrimAPI_MakeBox(theX, theY, theZ).Shape();
|
|
}
|
|
|
|
TopoDS_Shape translateShape(const TopoDS_Shape& theShape, const double theX, const double theY, const double theZ);
|
|
|
|
TopoDS_Shape makeBoxPlaced(const double theX, const double theY, const double theZ, const double theTx, const double theTy, const double theTz)
|
|
{
|
|
return translateShape(BRepPrimAPI_MakeBox(theX, theY, theZ).Shape(), theTx, theTy, theTz);
|
|
}
|
|
|
|
TopoDS_Shape translateShape(const TopoDS_Shape& theShape, const double theX, const double theY, const double theZ)
|
|
{
|
|
gp_Trsf aTransform;
|
|
aTransform.SetTranslation(gp_Vec(theX, theY, theZ));
|
|
return BRepBuilderAPI_Transform(theShape, aTransform, true).Shape();
|
|
}
|
|
|
|
TopoDS_Shape makeCylinder(const double theRadius, const double theHeight, const double theX, const double theY, const double theZ)
|
|
{
|
|
if (theRadius <= 0.0 || theHeight <= 0.0) throw std::invalid_argument("Cylinder radius and height must be positive.");
|
|
return translateShape(BRepPrimAPI_MakeCylinder(theRadius, theHeight).Shape(), theX, theY, theZ);
|
|
}
|
|
|
|
TopoDS_Shape makeSphere(const double theRadius, const double theX, const double theY, const double theZ)
|
|
{
|
|
if (theRadius <= 0.0) throw std::invalid_argument("Sphere radius must be positive.");
|
|
return translateShape(BRepPrimAPI_MakeSphere(theRadius).Shape(), theX, theY, theZ);
|
|
}
|
|
|
|
TopoDS_Shape makeCone(const double theRadius1, const double theRadius2, const double theHeight, const double theX, const double theY, const double theZ)
|
|
{
|
|
if (theRadius1 < 0.0 || theRadius2 < 0.0 || theRadius1 + theRadius2 <= 0.0 || theHeight <= 0.0) throw std::invalid_argument("Cone radii and height are invalid.");
|
|
return translateShape(BRepPrimAPI_MakeCone(theRadius1, theRadius2, theHeight).Shape(), theX, theY, theZ);
|
|
}
|
|
|
|
TopoDS_Shape makeRectangleFace(const double theWidth, const double theHeight)
|
|
{
|
|
if (theWidth <= 0.0 || theHeight <= 0.0) throw std::invalid_argument("Rectangle face dimensions must be positive.");
|
|
BRepBuilderAPI_MakePolygon aPolygon;
|
|
aPolygon.Add(gp_Pnt(0.0, 0.0, 0.0));
|
|
aPolygon.Add(gp_Pnt(theWidth, 0.0, 0.0));
|
|
aPolygon.Add(gp_Pnt(theWidth, theHeight, 0.0));
|
|
aPolygon.Add(gp_Pnt(0.0, theHeight, 0.0));
|
|
aPolygon.Close();
|
|
return BRepBuilderAPI_MakeFace(aPolygon.Wire()).Face();
|
|
}
|
|
|
|
TopoDS_Shape makeRectangleFacePlaced(const double theWidth, const double theHeight, const double theTx, const double theTy, const double theTz)
|
|
{
|
|
return translateShape(makeRectangleFace(theWidth, theHeight), theTx, theTy, theTz);
|
|
}
|
|
|
|
TopoDS_Shape makeLineWire(const double theStartX,
|
|
const double theStartY,
|
|
const double theStartZ,
|
|
const double theEndX,
|
|
const double theEndY,
|
|
const double theEndZ)
|
|
{
|
|
const gp_Pnt aStart(theStartX, theStartY, theStartZ);
|
|
const gp_Pnt anEnd(theEndX, theEndY, theEndZ);
|
|
if (aStart.Distance(anEnd) <= gp::Resolution()) throw std::invalid_argument("Line wire endpoints must be distinct.");
|
|
BRepBuilderAPI_MakePolygon aPolygon;
|
|
aPolygon.Add(aStart);
|
|
aPolygon.Add(anEnd);
|
|
return aPolygon.Wire();
|
|
}
|
|
|
|
|
|
std::string occtVersion()
|
|
{
|
|
return OCC_VERSION_COMPLETE;
|
|
}
|
|
|
|
emscripten::val booleanHistory(const TopoDS_Shape& theObject,
|
|
const TopoDS_Shape& theTool,
|
|
const std::string& theOperation)
|
|
{
|
|
if (theOperation == "fuse")
|
|
{
|
|
BRepAlgoAPI_Fuse anAlgorithm(theObject, theTool);
|
|
return runBoolean(anAlgorithm, theObject, theTool);
|
|
}
|
|
if (theOperation == "cut")
|
|
{
|
|
BRepAlgoAPI_Cut anAlgorithm(theObject, theTool);
|
|
return runBoolean(anAlgorithm, theObject, theTool);
|
|
}
|
|
if (theOperation == "common")
|
|
{
|
|
BRepAlgoAPI_Common anAlgorithm(theObject, theTool);
|
|
return runBoolean(anAlgorithm, theObject, theTool);
|
|
}
|
|
throw std::invalid_argument("Unsupported Boolean operation. Expected fuse, cut or common.");
|
|
}
|
|
|
|
emscripten::val booleanProbe(const TopoDS_Shape& theObject,
|
|
const TopoDS_Shape& theTool,
|
|
const std::string& theOperation)
|
|
{
|
|
if (theOperation == "fuse") { BRepAlgoAPI_Fuse anAlgorithm(theObject, theTool); return runBooleanProbe(anAlgorithm); }
|
|
if (theOperation == "cut") { BRepAlgoAPI_Cut anAlgorithm(theObject, theTool); return runBooleanProbe(anAlgorithm); }
|
|
if (theOperation == "common") { BRepAlgoAPI_Common anAlgorithm(theObject, theTool); return runBooleanProbe(anAlgorithm); }
|
|
throw std::invalid_argument("Unsupported Boolean operation. Expected fuse, cut or common.");
|
|
}
|
|
|
|
TopoDS_Shape booleanResult(const TopoDS_Shape& theObject,
|
|
const TopoDS_Shape& theTool,
|
|
const std::string& theOperation)
|
|
{
|
|
if (theOperation == "fuse") { BRepAlgoAPI_Fuse anAlgorithm(theObject, theTool); anAlgorithm.Build(); if (anAlgorithm.IsDone() && !anAlgorithm.HasErrors()) return anAlgorithm.Shape(); }
|
|
else if (theOperation == "cut") { BRepAlgoAPI_Cut anAlgorithm(theObject, theTool); anAlgorithm.Build(); if (anAlgorithm.IsDone() && !anAlgorithm.HasErrors()) return anAlgorithm.Shape(); }
|
|
else if (theOperation == "common") { BRepAlgoAPI_Common anAlgorithm(theObject, theTool); anAlgorithm.Build(); if (anAlgorithm.IsDone() && !anAlgorithm.HasErrors()) return anAlgorithm.Shape(); }
|
|
else throw std::invalid_argument("Unsupported Boolean operation. Expected fuse, cut or common.");
|
|
throw std::runtime_error("OCCT boolean result failed.");
|
|
}
|
|
|
|
TopoDS_Shape readStep(const std::string& theText)
|
|
{
|
|
if (theText.empty())
|
|
{
|
|
throw std::invalid_argument("STEP text must not be empty.");
|
|
}
|
|
std::istringstream aStream(theText);
|
|
STEPControl_Reader aReader;
|
|
const IFSelect_ReturnStatus aStatus = aReader.ReadStream("bitbybit.step", aStream);
|
|
if (aStatus != IFSelect_RetDone || aReader.NbRootsForTransfer() <= 0 || !aReader.TransferRoots())
|
|
{
|
|
throw std::runtime_error("OCCT could not read and transfer the supplied STEP text.");
|
|
}
|
|
const TopoDS_Shape aShape = aReader.OneShape();
|
|
if (aShape.IsNull())
|
|
{
|
|
throw std::runtime_error("OCCT STEP transfer produced a null shape.");
|
|
}
|
|
return aShape;
|
|
}
|
|
|
|
std::string writeStep(const TopoDS_Shape& theShape)
|
|
{
|
|
if (theShape.IsNull())
|
|
{
|
|
throw std::invalid_argument("Cannot serialize a null OCCT shape.");
|
|
}
|
|
completePCurves(theShape);
|
|
STEPControl_Writer aWriter;
|
|
if (aWriter.Transfer(theShape, STEPControl_AsIs) != IFSelect_RetDone)
|
|
{
|
|
throw std::runtime_error("OCCT could not transfer the shape to STEP.");
|
|
}
|
|
std::ostringstream aStream;
|
|
if (aWriter.WriteStream(aStream) != IFSelect_RetDone)
|
|
{
|
|
throw std::runtime_error("OCCT could not write the shape to STEP.");
|
|
}
|
|
return aStream.str();
|
|
}
|
|
|
|
std::string writeBrep(const TopoDS_Shape& theShape)
|
|
{
|
|
if (theShape.IsNull()) throw std::invalid_argument("Cannot serialize a null OCCT shape.");
|
|
std::ostringstream aStream;
|
|
BRepTools::Write(theShape, aStream);
|
|
if (!aStream.good() || aStream.str().empty()) throw std::runtime_error("OCCT could not write the shape to BREP.");
|
|
return aStream.str();
|
|
}
|
|
|
|
emscripten::val booleanHistoryFromStep(const std::string& theObjectStep,
|
|
const std::string& theToolStep,
|
|
const std::string& theOperation)
|
|
{
|
|
return booleanHistory(readStep(theObjectStep), readStep(theToolStep), theOperation);
|
|
}
|
|
|
|
emscripten::val rotateHistory(const TopoDS_Shape& theShape,
|
|
const double theAxisOriginX,
|
|
const double theAxisOriginY,
|
|
const double theAxisOriginZ,
|
|
const double theAxisDirectionX,
|
|
const double theAxisDirectionY,
|
|
const double theAxisDirectionZ,
|
|
const double theAngleDegrees)
|
|
{
|
|
if (theShape.IsNull()) throw std::invalid_argument("Rotate shape must not be null.");
|
|
const gp_Vec aDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
|
|
if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Rotate axis direction must be non-zero.");
|
|
if (!std::isfinite(theAngleDegrees) || theAngleDegrees == 0.0 || std::abs(theAngleDegrees) > 360.0) throw std::invalid_argument("Rotate angle must be non-zero and within 360 degrees.");
|
|
constexpr double aPi = 3.14159265358979323846;
|
|
gp_Trsf aTransform;
|
|
aTransform.SetRotation(
|
|
gp_Ax1(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ)),
|
|
theAngleDegrees * aPi / 180.0);
|
|
BRepBuilderAPI_Transform anAlgorithm(theShape, aTransform, false, false);
|
|
if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT rotation failed while collecting native history.");
|
|
const TopoDS_Shape aResult = anAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
|
|
unsigned int aRecordIndex = 0;
|
|
bool hasDeleted = false;
|
|
for (const TopAbs_ShapeEnum aKind : aKinds)
|
|
{
|
|
const auto aSubShapes = subShapes(theShape, aKind);
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
|
|
{
|
|
const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
|
|
const auto& aModified = anAlgorithm.Modified(anInput);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next())
|
|
{
|
|
const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKind);
|
|
if (anOutputIndex < 0) continue;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
}
|
|
if (anAlgorithm.IsDeleted(anInput))
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "deleted"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1);
|
|
aRecords.set(aRecordIndex++, aRecord); hasDeleted = true;
|
|
}
|
|
}
|
|
}
|
|
if (aRecordIndex == 0) throw std::runtime_error("OCCT rotation produced no verifiable shape history.");
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
|
|
aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
|
|
aResponse.set("hasModified", true); aResponse.set("hasGenerated", false); aResponse.set("hasDeleted", hasDeleted);
|
|
aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
emscripten::val rotateHistoryFromStep(const std::string& theShapeStep,
|
|
const double theAxisOriginX,
|
|
const double theAxisOriginY,
|
|
const double theAxisOriginZ,
|
|
const double theAxisDirectionX,
|
|
const double theAxisDirectionY,
|
|
const double theAxisDirectionZ,
|
|
const double theAngleDegrees)
|
|
{
|
|
return rotateHistory(readStep(theShapeStep), theAxisOriginX, theAxisOriginY, theAxisOriginZ, theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ, theAngleDegrees);
|
|
}
|
|
|
|
emscripten::val prismHistory(const TopoDS_Shape& theProfile,
|
|
const double theDx,
|
|
const double theDy,
|
|
const double theDz)
|
|
{
|
|
if (theProfile.IsNull()) throw std::invalid_argument("Prism profile must not be null.");
|
|
const gp_Vec aVector(theDx, theDy, theDz);
|
|
if (aVector.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Prism direction must be non-zero.");
|
|
BRepPrimAPI_MakePrism anAlgorithm(theProfile, aVector, false, true);
|
|
anAlgorithm.Build();
|
|
if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT prism failed while collecting native history.");
|
|
const TopoDS_Shape aResult = anAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
|
|
unsigned int aRecordIndex = 0;
|
|
bool hasModified = false;
|
|
bool hasGenerated = false;
|
|
bool hasDeleted = false;
|
|
for (const TopAbs_ShapeEnum aKind : aKinds)
|
|
{
|
|
const auto aSubShapes = subShapes(theProfile, aKind);
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
|
|
{
|
|
const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
|
|
const TopoDS_Shape aFirst = anAlgorithm.FirstShape(anInput);
|
|
const int aFirstIndex = resultIndex(aFirst, aResult, aKind);
|
|
if (aFirstIndex >= 0)
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", aFirstIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
hasModified = true;
|
|
}
|
|
const auto& aGenerated = anAlgorithm.Generated(anInput);
|
|
const TopAbs_ShapeEnum aGeneratedKind = aKind == TopAbs_VERTEX ? TopAbs_EDGE : aKind == TopAbs_EDGE ? TopAbs_FACE : TopAbs_SOLID;
|
|
if (aGeneratedKind != TopAbs_SOLID) for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
|
|
{
|
|
const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aGeneratedKind);
|
|
if (anOutputIndex < 0) continue;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "generated"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("resultKind", kindName(aGeneratedKind)); aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
hasGenerated = true;
|
|
}
|
|
if (anAlgorithm.IsDeleted(anInput))
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "deleted"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
hasDeleted = true;
|
|
}
|
|
}
|
|
}
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
|
|
aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
|
|
aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted);
|
|
aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
emscripten::val prismHistoryFromStep(const std::string& theProfileStep,
|
|
const double theDx,
|
|
const double theDy,
|
|
const double theDz)
|
|
{
|
|
return prismHistory(readStep(theProfileStep), theDx, theDy, theDz);
|
|
}
|
|
|
|
TopoDS_Wire loftProfileWire(const TopoDS_Shape& theShape)
|
|
{
|
|
if (theShape.IsNull()) throw std::invalid_argument("Loft profile must not be null.");
|
|
if (theShape.ShapeType() == TopAbs_WIRE) return TopoDS::Wire(theShape);
|
|
if (theShape.ShapeType() == TopAbs_FACE) return BRepTools::OuterWire(TopoDS::Face(theShape));
|
|
TopoDS_Wire aBest;
|
|
double aBestArea = -1.0;
|
|
for (TopExp_Explorer anExplorer(theShape, TopAbs_FACE); anExplorer.More(); anExplorer.Next())
|
|
{
|
|
const TopoDS_Face aFace = TopoDS::Face(anExplorer.Current());
|
|
GProp_GProps aProperties;
|
|
BRepGProp::SurfaceProperties(aFace, aProperties);
|
|
if (aProperties.Mass() > aBestArea) { aBestArea = aProperties.Mass(); aBest = BRepTools::OuterWire(aFace); }
|
|
}
|
|
if (!aBest.IsNull()) return aBest;
|
|
for (TopExp_Explorer anExplorer(theShape, TopAbs_WIRE); anExplorer.More(); anExplorer.Next()) return TopoDS::Wire(anExplorer.Current());
|
|
throw std::invalid_argument("Loft profile must contain a face or wire.");
|
|
}
|
|
|
|
emscripten::val loftHistoryFromStep(const std::string& theFirstStep,
|
|
const std::string& theSecondStep,
|
|
const bool theRuled)
|
|
{
|
|
const TopoDS_Shape aFirstShape = readStep(theFirstStep);
|
|
const TopoDS_Shape aSecondShape = readStep(theSecondStep);
|
|
const TopoDS_Wire aFirstWire = loftProfileWire(aFirstShape);
|
|
const TopoDS_Wire aSecondWire = loftProfileWire(aSecondShape);
|
|
BRepOffsetAPI_ThruSections anAlgorithm(true, theRuled);
|
|
anAlgorithm.SetMutableInput(false);
|
|
anAlgorithm.AddWire(aFirstWire);
|
|
anAlgorithm.AddWire(aSecondWire);
|
|
anAlgorithm.Build();
|
|
if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT loft failed while collecting native history.");
|
|
const TopoDS_Shape aResult = anAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
unsigned int aRecordIndex = 0;
|
|
bool hasModified = false;
|
|
bool hasGenerated = false;
|
|
bool hasDeleted = false;
|
|
const TopoDS_Wire aWires[] = {aFirstWire, aSecondWire};
|
|
const char* aSources[] = {"object", "tool"};
|
|
const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE};
|
|
for (unsigned int aWireIndex = 0; aWireIndex < 2; ++aWireIndex)
|
|
{
|
|
for (const TopAbs_ShapeEnum aKind : aKinds)
|
|
{
|
|
const auto aSubShapes = subShapes(aWires[aWireIndex], aKind);
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
|
|
{
|
|
const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
|
|
const auto& aGenerated = anAlgorithm.Generated(anInput);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
|
|
{
|
|
const TopoDS_Shape& anOutput = anIterator.Value();
|
|
const int anOutputIndex = resultIndex(anOutput, aResult, anOutput.ShapeType());
|
|
if (anOutputIndex < 0) continue;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "generated"); aRecord.set("source", aSources[aWireIndex]); aRecord.set("kind", kindName(aKind)); aRecord.set("resultKind", kindName(anOutput.ShapeType()));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex); aRecords.set(aRecordIndex++, aRecord); hasGenerated = true;
|
|
}
|
|
if (anAlgorithm.IsDeleted(anInput))
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object(); aRecord.set("relation", "deleted"); aRecord.set("source", aSources[aWireIndex]); aRecord.set("kind", kindName(aKind)); aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1); aRecords.set(aRecordIndex++, aRecord); hasDeleted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (aRecordIndex == 0) throw std::runtime_error("OCCT loft produced no verifiable profile history.");
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE); aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
|
|
aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted); aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
TopoDS_Face pipeProfileFace(const TopoDS_Shape& theShape)
|
|
{
|
|
if (theShape.IsNull()) throw std::invalid_argument("Pipe profile must not be null.");
|
|
if (theShape.ShapeType() == TopAbs_FACE) return TopoDS::Face(theShape);
|
|
TopoDS_Face aBest;
|
|
double aBestArea = -1.0;
|
|
for (TopExp_Explorer anExplorer(theShape, TopAbs_FACE); anExplorer.More(); anExplorer.Next())
|
|
{
|
|
const TopoDS_Face aFace = TopoDS::Face(anExplorer.Current());
|
|
GProp_GProps aProperties;
|
|
BRepGProp::SurfaceProperties(aFace, aProperties);
|
|
if (aProperties.Mass() > aBestArea) { aBestArea = aProperties.Mass(); aBest = aFace; }
|
|
}
|
|
if (aBest.IsNull()) throw std::invalid_argument("Pipe profile must contain a face.");
|
|
return aBest;
|
|
}
|
|
|
|
TopoDS_Wire pipeSpineWire(const TopoDS_Shape& theShape)
|
|
{
|
|
if (theShape.IsNull()) throw std::invalid_argument("Pipe spine must not be null.");
|
|
if (theShape.ShapeType() == TopAbs_WIRE) return TopoDS::Wire(theShape);
|
|
for (TopExp_Explorer anExplorer(theShape, TopAbs_WIRE); anExplorer.More(); anExplorer.Next()) return TopoDS::Wire(anExplorer.Current());
|
|
BRepBuilderAPI_MakeWire aWire;
|
|
for (TopExp_Explorer anExplorer(theShape, TopAbs_EDGE); anExplorer.More(); anExplorer.Next()) aWire.Add(TopoDS::Edge(anExplorer.Current()));
|
|
if (!aWire.IsDone()) throw std::invalid_argument("Pipe spine must contain a connected wire.");
|
|
return aWire.Wire();
|
|
}
|
|
|
|
emscripten::val pipeHistoryFromStep(const std::string& theProfileStep,
|
|
const std::string& theSpineStep)
|
|
{
|
|
const TopoDS_Face aProfile = pipeProfileFace(readStep(theProfileStep));
|
|
const TopoDS_Wire aSpine = pipeSpineWire(readStep(theSpineStep));
|
|
const auto aSpineEdges = subShapes(aSpine, TopAbs_EDGE);
|
|
const auto aSpineVertices = subShapes(aSpine, TopAbs_VERTEX);
|
|
if (aSpineEdges.size() != 1 || aSpineVertices.size() != 2) throw std::invalid_argument("Native Pipe history currently requires a single-edge open spine.");
|
|
|
|
BRepOffsetAPI_MakePipe anAlgorithm(aSpine, aProfile);
|
|
anAlgorithm.Build();
|
|
if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT pipe failed while collecting native history.");
|
|
const TopoDS_Shape aResult = anAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
unsigned int aRecordIndex = 0;
|
|
bool hasModified = false;
|
|
bool hasGenerated = false;
|
|
|
|
const auto appendRecord = [&](const char* theRelation,
|
|
const char* theSource,
|
|
const TopAbs_ShapeEnum theSourceKind,
|
|
const int theSourceIndex,
|
|
const TopoDS_Shape& theOutput)
|
|
{
|
|
if (theOutput.IsNull()) return;
|
|
const int anOutputIndex = resultIndex(theOutput, aResult, theOutput.ShapeType());
|
|
if (anOutputIndex < 0) return;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", theRelation); aRecord.set("source", theSource); aRecord.set("kind", kindName(theSourceKind));
|
|
aRecord.set("resultKind", kindName(theOutput.ShapeType())); aRecord.set("sourceIndex", theSourceIndex); aRecord.set("resultIndex", anOutputIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
hasModified = hasModified || std::string(theRelation) == "modified";
|
|
hasGenerated = hasGenerated || std::string(theRelation) == "generated";
|
|
};
|
|
|
|
const TopAbs_ShapeEnum aProfileKinds[] = {TopAbs_VERTEX, TopAbs_EDGE};
|
|
for (const TopAbs_ShapeEnum aKind : aProfileKinds)
|
|
{
|
|
const auto aSources = subShapes(aProfile, aKind);
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aSources.size(); ++aSourceIndex)
|
|
{
|
|
const auto& aGenerated = anAlgorithm.Generated(aSources[aSourceIndex]);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
|
|
{
|
|
appendRecord("generated", "object", aKind, static_cast<int>(aSourceIndex), anIterator.Value());
|
|
}
|
|
}
|
|
}
|
|
appendRecord("modified", "object", TopAbs_FACE, 0, anAlgorithm.FirstShape());
|
|
appendRecord("generated", "object", TopAbs_FACE, 0, anAlgorithm.LastShape());
|
|
|
|
const auto& aSpineEdge = aSpineEdges[0];
|
|
for (const TopAbs_ShapeEnum aKind : aProfileKinds)
|
|
{
|
|
const auto aProfileSources = subShapes(aProfile, aKind);
|
|
for (const TopoDS_Shape& aProfileSource : aProfileSources)
|
|
{
|
|
appendRecord("generated", "tool", TopAbs_EDGE, 0, anAlgorithm.Generated(aSpineEdge, aProfileSource));
|
|
}
|
|
}
|
|
appendRecord("generated", "tool", TopAbs_VERTEX, 0, anAlgorithm.FirstShape());
|
|
appendRecord("generated", "tool", TopAbs_VERTEX, 1, anAlgorithm.LastShape());
|
|
if (aRecordIndex == 0) throw std::runtime_error("OCCT pipe produced no verifiable profile or spine history.");
|
|
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE); aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
|
|
aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", false); aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
|
|
emscripten::val pocketHistory(const TopoDS_Shape& theBase,
|
|
const TopoDS_Shape& theProfile,
|
|
const double theDx,
|
|
const double theDy,
|
|
const double theDz)
|
|
{
|
|
if (theBase.IsNull()) throw std::invalid_argument("Pocket base must not be null.");
|
|
if (theProfile.IsNull()) throw std::invalid_argument("Pocket profile must not be null.");
|
|
const gp_Vec aVector(theDx, theDy, theDz);
|
|
if (aVector.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Pocket direction must be non-zero.");
|
|
BRepPrimAPI_MakePrism aPrism(theProfile, aVector, false, true);
|
|
aPrism.Build();
|
|
if (!aPrism.IsDone()) throw std::runtime_error("OCCT pocket profile extrusion failed while collecting native history.");
|
|
TopoDS_Shape aTool = aPrism.Shape();
|
|
BRepAlgoAPI_Cut anAlgorithm(theBase, aTool);
|
|
return runBoolean(anAlgorithm, theBase, aTool);
|
|
}
|
|
|
|
emscripten::val pocketHistoryFromStep(const std::string& theBaseStep,
|
|
const std::string& theProfileStep,
|
|
const double theDx,
|
|
const double theDy,
|
|
const double theDz)
|
|
{
|
|
return pocketHistory(readStep(theBaseStep), readStep(theProfileStep), theDx, theDy, theDz);
|
|
}
|
|
|
|
emscripten::val revolutionHistory(const TopoDS_Shape& theProfile,
|
|
const double theAxisOriginX,
|
|
const double theAxisOriginY,
|
|
const double theAxisOriginZ,
|
|
const double theAxisDirectionX,
|
|
const double theAxisDirectionY,
|
|
const double theAxisDirectionZ,
|
|
const double theAngleDegrees)
|
|
{
|
|
if (theProfile.IsNull()) throw std::invalid_argument("Revolution profile must not be null.");
|
|
const gp_Vec aDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
|
|
if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Revolution axis direction must be non-zero.");
|
|
if (!std::isfinite(theAngleDegrees) || theAngleDegrees <= 0.0 || theAngleDegrees > 360.0) throw std::invalid_argument("Revolution angle must be in (0, 360] degrees.");
|
|
const gp_Ax1 anAxis(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ));
|
|
constexpr double aPi = 3.14159265358979323846;
|
|
BRepPrimAPI_MakeRevol anAlgorithm(theProfile, anAxis, theAngleDegrees * aPi / 180.0, false);
|
|
anAlgorithm.Build();
|
|
if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT revolution failed while collecting native history.");
|
|
const TopoDS_Shape aResult = anAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
|
|
unsigned int aRecordIndex = 0;
|
|
bool hasModified = false;
|
|
bool hasGenerated = false;
|
|
bool hasDeleted = false;
|
|
for (const TopAbs_ShapeEnum aKind : aKinds)
|
|
{
|
|
const auto aSubShapes = subShapes(theProfile, aKind);
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aSubShapes.size(); ++aSourceIndex)
|
|
{
|
|
const TopoDS_Shape& anInput = aSubShapes[aSourceIndex];
|
|
const TopoDS_Shape aFirst = anAlgorithm.FirstShape(anInput);
|
|
const int aFirstIndex = resultIndex(aFirst, aResult, aKind);
|
|
if (aFirstIndex >= 0)
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "modified"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", aFirstIndex);
|
|
aRecords.set(aRecordIndex++, aRecord); hasModified = true;
|
|
}
|
|
const auto& aGenerated = anAlgorithm.Generated(anInput);
|
|
const TopAbs_ShapeEnum aGeneratedKind = aKind == TopAbs_VERTEX ? TopAbs_EDGE : aKind == TopAbs_EDGE ? TopAbs_FACE : TopAbs_SOLID;
|
|
if (aGeneratedKind != TopAbs_SOLID) for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aGenerated); anIterator.More(); anIterator.Next())
|
|
{
|
|
const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aGeneratedKind);
|
|
if (anOutputIndex < 0) continue;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "generated"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("resultKind", kindName(aGeneratedKind)); aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex);
|
|
aRecords.set(aRecordIndex++, aRecord); hasGenerated = true;
|
|
}
|
|
if (anAlgorithm.IsDeleted(anInput))
|
|
{
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", "deleted"); aRecord.set("source", "object"); aRecord.set("kind", kindName(aKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(aSourceIndex)); aRecord.set("resultIndex", -1);
|
|
aRecords.set(aRecordIndex++, aRecord); hasDeleted = true;
|
|
}
|
|
}
|
|
}
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE);
|
|
aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult)); aResponse.set("records", aRecords);
|
|
aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted);
|
|
aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
emscripten::val revolutionHistoryFromStep(const std::string& theProfileStep,
|
|
const double theAxisOriginX,
|
|
const double theAxisOriginY,
|
|
const double theAxisOriginZ,
|
|
const double theAxisDirectionX,
|
|
const double theAxisDirectionY,
|
|
const double theAxisDirectionZ,
|
|
const double theAngleDegrees)
|
|
{
|
|
return revolutionHistory(readStep(theProfileStep), theAxisOriginX, theAxisOriginY, theAxisOriginZ, theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ, theAngleDegrees);
|
|
}
|
|
|
|
emscripten::val grooveHistory(const TopoDS_Shape& theBase,
|
|
const TopoDS_Shape& theProfile,
|
|
const double theAxisOriginX,
|
|
const double theAxisOriginY,
|
|
const double theAxisOriginZ,
|
|
const double theAxisDirectionX,
|
|
const double theAxisDirectionY,
|
|
const double theAxisDirectionZ,
|
|
const double theAngleDegrees)
|
|
{
|
|
if (theBase.IsNull()) throw std::invalid_argument("Groove base must not be null.");
|
|
if (theProfile.IsNull()) throw std::invalid_argument("Groove profile must not be null.");
|
|
const gp_Vec aDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
|
|
if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Groove axis direction must be non-zero.");
|
|
if (!std::isfinite(theAngleDegrees) || theAngleDegrees <= 0.0 || theAngleDegrees > 360.0) throw std::invalid_argument("Groove angle must be in (0, 360] degrees.");
|
|
const gp_Ax1 anAxis(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ));
|
|
constexpr double aPi = 3.14159265358979323846;
|
|
BRepPrimAPI_MakeRevol aRevolution(theProfile, anAxis, theAngleDegrees * aPi / 180.0, false);
|
|
aRevolution.Build();
|
|
if (!aRevolution.IsDone()) throw std::runtime_error("OCCT groove profile revolution failed while collecting native history.");
|
|
TopoDS_Shape aTool = aRevolution.Shape();
|
|
BRepAlgoAPI_Cut anAlgorithm(theBase, aTool);
|
|
return runBoolean(anAlgorithm, theBase, aTool);
|
|
}
|
|
|
|
emscripten::val grooveHistoryFromStep(const std::string& theBaseStep,
|
|
const std::string& theProfileStep,
|
|
const double theAxisOriginX,
|
|
const double theAxisOriginY,
|
|
const double theAxisOriginZ,
|
|
const double theAxisDirectionX,
|
|
const double theAxisDirectionY,
|
|
const double theAxisDirectionZ,
|
|
const double theAngleDegrees)
|
|
{
|
|
return grooveHistory(readStep(theBaseStep), readStep(theProfileStep), theAxisOriginX, theAxisOriginY, theAxisOriginZ, theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ, theAngleDegrees);
|
|
}
|
|
|
|
emscripten::val filletHistoryFromStep(const std::string& theBaseStep,
|
|
const double theRadius)
|
|
{
|
|
if (!std::isfinite(theRadius) || theRadius <= 0.0) throw std::invalid_argument("Fillet radius must be finite and positive.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
BRepFilletAPI_MakeFillet anAlgorithm(aBase);
|
|
for (TopExp_Explorer anExplorer(aBase, TopAbs_EDGE); anExplorer.More(); anExplorer.Next())
|
|
{
|
|
anAlgorithm.Add(theRadius, TopoDS::Edge(anExplorer.Current()));
|
|
}
|
|
return filletHistory(anAlgorithm, aBase);
|
|
}
|
|
|
|
emscripten::val chamferHistoryFromStep(const std::string& theBaseStep,
|
|
const double theDistance)
|
|
{
|
|
if (!std::isfinite(theDistance) || theDistance <= 0.0) throw std::invalid_argument("Chamfer distance must be finite and positive.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
BRepFilletAPI_MakeChamfer anAlgorithm(aBase);
|
|
for (TopExp_Explorer anExplorer(aBase, TopAbs_EDGE); anExplorer.More(); anExplorer.Next())
|
|
{
|
|
anAlgorithm.Add(theDistance, TopoDS::Edge(anExplorer.Current()));
|
|
}
|
|
return chamferHistory(anAlgorithm, aBase);
|
|
}
|
|
|
|
emscripten::val draftHistoryFromStep(const std::string& theBaseStep,
|
|
const int theFaceIndex,
|
|
const double theAngle,
|
|
const double theDirectionX,
|
|
const double theDirectionY,
|
|
const double theDirectionZ,
|
|
const double theNeutralPlaneOriginX,
|
|
const double theNeutralPlaneOriginY,
|
|
const double theNeutralPlaneOriginZ,
|
|
const double theNeutralPlaneDirectionX,
|
|
const double theNeutralPlaneDirectionY,
|
|
const double theNeutralPlaneDirectionZ,
|
|
const bool theReversed)
|
|
{
|
|
if (!std::isfinite(theAngle) || theAngle == 0.0 || theAngle <= -89.999 || theAngle >= 89.999) throw std::invalid_argument("Draft angle must be finite, non-zero and between -89.999 and 89.999 degrees.");
|
|
const gp_Vec aDirection(theDirectionX, theDirectionY, theDirectionZ);
|
|
const gp_Vec aNeutralDirection(theNeutralPlaneDirectionX, theNeutralPlaneDirectionY, theNeutralPlaneDirectionZ);
|
|
if (aDirection.Magnitude() <= gp::Resolution() || aNeutralDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Draft directions must be non-zero.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
const auto aFaces = subShapes(aBase, TopAbs_FACE);
|
|
if (theFaceIndex < 0 || static_cast<std::size_t>(theFaceIndex) >= aFaces.size()) throw std::out_of_range("Draft face index is outside the base face list.");
|
|
BRepOffsetAPI_DraftAngle anAlgorithm(aBase);
|
|
const gp_Pln aNeutralPlane(gp_Pnt(theNeutralPlaneOriginX, theNeutralPlaneOriginY, theNeutralPlaneOriginZ), gp_Dir(theNeutralPlaneDirectionX, theNeutralPlaneDirectionY, theNeutralPlaneDirectionZ));
|
|
constexpr double aPi = 3.14159265358979323846;
|
|
anAlgorithm.Add(TopoDS::Face(aFaces[static_cast<std::size_t>(theFaceIndex)]), gp_Dir(theDirectionX, theDirectionY, theDirectionZ), theAngle * aPi / 180.0, aNeutralPlane, !theReversed);
|
|
if (!anAlgorithm.AddDone()) throw std::runtime_error("OCCT draft failed while collecting native history.");
|
|
return runDressupHistory(anAlgorithm, aBase, "draft");
|
|
}
|
|
|
|
emscripten::val thicknessHistoryFromStep(const std::string& theBaseStep,
|
|
const int theFaceIndex,
|
|
const double theOffset,
|
|
const bool theIntersectionJoin)
|
|
{
|
|
if (!std::isfinite(theOffset) || theOffset == 0.0) throw std::invalid_argument("Thickness offset must be finite and non-zero.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
const auto aFaces = subShapes(aBase, TopAbs_FACE);
|
|
if (theFaceIndex < 0 || static_cast<std::size_t>(theFaceIndex) >= aFaces.size()) throw std::out_of_range("Thickness face index is outside the base face list.");
|
|
NCollection_List<TopoDS_Shape> aClosingFaces;
|
|
aClosingFaces.Append(aFaces[static_cast<std::size_t>(theFaceIndex)]);
|
|
BRepOffsetAPI_MakeThickSolid anAlgorithm;
|
|
anAlgorithm.MakeThickSolidByJoin(aBase,
|
|
aClosingFaces,
|
|
theOffset,
|
|
1.0e-3,
|
|
BRepOffset_Skin,
|
|
theIntersectionJoin,
|
|
false,
|
|
theIntersectionJoin ? GeomAbs_Intersection : GeomAbs_Arc,
|
|
false);
|
|
if (!anAlgorithm.IsDone()) throw std::runtime_error("OCCT thickness failed while collecting native history.");
|
|
return runDressupHistory(anAlgorithm, aBase, "thickness");
|
|
}
|
|
|
|
emscripten::val linearPatternHistoryFromStep(const std::string& theBaseStep,
|
|
const double theDx,
|
|
const double theDy,
|
|
const double theDz)
|
|
{
|
|
const gp_Vec aTranslation(theDx, theDy, theDz);
|
|
if (aTranslation.Magnitude() <= gp::Resolution()) throw std::invalid_argument("LinearPattern translation must be non-zero.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
gp_Trsf aTransform;
|
|
aTransform.SetTranslation(aTranslation);
|
|
const TopoDS_Shape aCopy = BRepBuilderAPI_Transform(aBase, aTransform, true).Shape();
|
|
BRepAlgoAPI_Fuse anAlgorithm(aBase, aCopy);
|
|
return runBoolean(anAlgorithm, aBase, aCopy);
|
|
}
|
|
|
|
emscripten::val polarPatternHistoryFromStep(const std::string& theBaseStep,
|
|
const double theAxisOriginX,
|
|
const double theAxisOriginY,
|
|
const double theAxisOriginZ,
|
|
const double theAxisDirectionX,
|
|
const double theAxisDirectionY,
|
|
const double theAxisDirectionZ,
|
|
const double theAngleDegrees)
|
|
{
|
|
const gp_Vec anAxisDirection(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ);
|
|
if (anAxisDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("PolarPattern axis direction must be non-zero.");
|
|
if (!std::isfinite(theAngleDegrees) || theAngleDegrees == 0.0 || std::abs(theAngleDegrees) > 360.0) throw std::invalid_argument("PolarPattern angle must be non-zero and within 360 degrees.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
constexpr double aPi = 3.14159265358979323846;
|
|
gp_Trsf aTransform;
|
|
aTransform.SetRotation(gp_Ax1(gp_Pnt(theAxisOriginX, theAxisOriginY, theAxisOriginZ), gp_Dir(theAxisDirectionX, theAxisDirectionY, theAxisDirectionZ)), theAngleDegrees * aPi / 180.0);
|
|
const TopoDS_Shape aCopy = BRepBuilderAPI_Transform(aBase, aTransform, true).Shape();
|
|
BRepAlgoAPI_Fuse anAlgorithm(aBase, aCopy);
|
|
return runBoolean(anAlgorithm, aBase, aCopy);
|
|
}
|
|
|
|
emscripten::val mirroredHistoryFromStep(const std::string& theBaseStep,
|
|
const double thePlaneOriginX,
|
|
const double thePlaneOriginY,
|
|
const double thePlaneOriginZ,
|
|
const double thePlaneNormalX,
|
|
const double thePlaneNormalY,
|
|
const double thePlaneNormalZ)
|
|
{
|
|
const gp_Vec aPlaneNormal(thePlaneNormalX, thePlaneNormalY, thePlaneNormalZ);
|
|
if (aPlaneNormal.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Mirrored plane normal must be non-zero.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
gp_Trsf aTransform;
|
|
aTransform.SetMirror(gp_Ax2(gp_Pnt(thePlaneOriginX, thePlaneOriginY, thePlaneOriginZ), gp_Dir(thePlaneNormalX, thePlaneNormalY, thePlaneNormalZ)));
|
|
const TopoDS_Shape aCopy = BRepBuilderAPI_Transform(aBase, aTransform, true).Shape();
|
|
BRepAlgoAPI_Fuse anAlgorithm(aBase, aCopy);
|
|
return runBoolean(anAlgorithm, aBase, aCopy);
|
|
}
|
|
|
|
struct TrackedTransformInstance
|
|
{
|
|
TopoDS_Shape shape;
|
|
std::array<std::vector<TopoDS_Shape>, 3> sourceShapes;
|
|
};
|
|
|
|
double finiteArrayValue(const emscripten::val& theArray, const unsigned int theIndex, const char* theLabel)
|
|
{
|
|
if (!emscripten::val::global("Array").call<bool>("isArray", theArray) || theArray["length"].as<unsigned int>() != 3) throw std::invalid_argument(std::string(theLabel) + " must contain three numbers.");
|
|
const double aValue = theArray[theIndex].as<double>();
|
|
if (!std::isfinite(aValue)) throw std::invalid_argument(std::string(theLabel) + " must contain finite numbers.");
|
|
return aValue;
|
|
}
|
|
|
|
emscripten::val multiTransformHistoryFromStep(const std::string& theBaseStep,
|
|
const emscripten::val& theSteps)
|
|
{
|
|
if (!emscripten::val::global("Array").call<bool>("isArray", theSteps)) throw std::invalid_argument("MultiTransform steps must be an array.");
|
|
const unsigned int aStepCount = theSteps["length"].as<unsigned int>();
|
|
if (aStepCount < 2 || aStepCount > 6) throw std::invalid_argument("Native ordered MultiTransform requires between two and six steps.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
TrackedTransformInstance aBaseInstance;
|
|
aBaseInstance.shape = aBase;
|
|
const TopAbs_ShapeEnum aKinds[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE};
|
|
for (std::size_t aKindIndex = 0; aKindIndex < 3; ++aKindIndex) aBaseInstance.sourceShapes[aKindIndex] = subShapes(aBase, aKinds[aKindIndex]);
|
|
std::vector<TrackedTransformInstance> anInstances = {aBaseInstance};
|
|
|
|
for (unsigned int aStepIndex = 0; aStepIndex < aStepCount; ++aStepIndex)
|
|
{
|
|
const emscripten::val aStep = theSteps[aStepIndex];
|
|
const std::string aType = aStep["type"].as<std::string>();
|
|
gp_Trsf aTransform;
|
|
if (aType == "linear")
|
|
{
|
|
const emscripten::val aDirection = aStep["direction"];
|
|
const gp_Vec aTranslation(finiteArrayValue(aDirection, 0, "Linear direction"), finiteArrayValue(aDirection, 1, "Linear direction"), finiteArrayValue(aDirection, 2, "Linear direction"));
|
|
if (aTranslation.Magnitude() <= gp::Resolution()) throw std::invalid_argument("MultiTransform linear translation must be non-zero.");
|
|
aTransform.SetTranslation(aTranslation);
|
|
}
|
|
else if (aType == "polar")
|
|
{
|
|
const emscripten::val anOrigin = aStep["axisOrigin"];
|
|
const emscripten::val aDirection = aStep["direction"];
|
|
const gp_Vec anAxis(finiteArrayValue(aDirection, 0, "Polar direction"), finiteArrayValue(aDirection, 1, "Polar direction"), finiteArrayValue(aDirection, 2, "Polar direction"));
|
|
const double anAngle = aStep["angle"].as<double>();
|
|
if (anAxis.Magnitude() <= gp::Resolution() || !std::isfinite(anAngle) || anAngle == 0.0 || std::abs(anAngle) > 360.0) throw std::invalid_argument("MultiTransform polar step requires a finite axis and angle.");
|
|
constexpr double aPi = 3.14159265358979323846;
|
|
aTransform.SetRotation(gp_Ax1(gp_Pnt(finiteArrayValue(anOrigin, 0, "Polar origin"), finiteArrayValue(anOrigin, 1, "Polar origin"), finiteArrayValue(anOrigin, 2, "Polar origin")), gp_Dir(anAxis)), anAngle * aPi / 180.0);
|
|
}
|
|
else if (aType == "mirrored")
|
|
{
|
|
const emscripten::val anOrigin = aStep["axisOrigin"];
|
|
const emscripten::val aDirection = aStep["direction"];
|
|
const gp_Vec aNormal(finiteArrayValue(aDirection, 0, "Mirror normal"), finiteArrayValue(aDirection, 1, "Mirror normal"), finiteArrayValue(aDirection, 2, "Mirror normal"));
|
|
if (aNormal.Magnitude() <= gp::Resolution()) throw std::invalid_argument("MultiTransform mirror normal must be non-zero.");
|
|
aTransform.SetMirror(gp_Ax2(gp_Pnt(finiteArrayValue(anOrigin, 0, "Mirror origin"), finiteArrayValue(anOrigin, 1, "Mirror origin"), finiteArrayValue(anOrigin, 2, "Mirror origin")), gp_Dir(aNormal)));
|
|
}
|
|
else throw std::invalid_argument("Unsupported MultiTransform step type.");
|
|
|
|
const std::vector<TrackedTransformInstance> aParents = anInstances;
|
|
for (const TrackedTransformInstance& aParent : aParents)
|
|
{
|
|
BRepBuilderAPI_Transform aTransformer(aParent.shape, aTransform, true);
|
|
if (!aTransformer.IsDone()) throw std::runtime_error("OCCT MultiTransform instance creation failed.");
|
|
TrackedTransformInstance aChild;
|
|
aChild.shape = aTransformer.Shape();
|
|
for (std::size_t aKindIndex = 0; aKindIndex < 3; ++aKindIndex)
|
|
{
|
|
for (const TopoDS_Shape& aParentSource : aParent.sourceShapes[aKindIndex])
|
|
{
|
|
const TopoDS_Shape aChildSource = aTransformer.ModifiedShape(aParentSource);
|
|
if (aChildSource.IsNull()) throw std::runtime_error("OCCT MultiTransform lost a source subshape mapping.");
|
|
aChild.sourceShapes[aKindIndex].push_back(aChildSource);
|
|
}
|
|
}
|
|
anInstances.push_back(std::move(aChild));
|
|
}
|
|
if (anInstances.size() > 100) throw std::invalid_argument("MultiTransform cannot create more than 100 instances.");
|
|
}
|
|
|
|
NCollection_List<TopoDS_Shape> anArguments;
|
|
NCollection_List<TopoDS_Shape> aTools;
|
|
anArguments.Append(anInstances.front().shape);
|
|
for (std::size_t anInstanceIndex = 1; anInstanceIndex < anInstances.size(); ++anInstanceIndex) aTools.Append(anInstances[anInstanceIndex].shape);
|
|
BRepAlgoAPI_Fuse anAlgorithm;
|
|
anAlgorithm.SetArguments(anArguments);
|
|
anAlgorithm.SetTools(aTools);
|
|
anAlgorithm.Build();
|
|
if (!anAlgorithm.IsDone() || anAlgorithm.HasErrors()) throw std::runtime_error("OCCT ordered MultiTransform fuse failed while collecting native history.");
|
|
const TopoDS_Shape aResult = anAlgorithm.Shape();
|
|
emscripten::val aRecords = emscripten::val::array();
|
|
unsigned int aRecordIndex = 0;
|
|
bool hasModified = false;
|
|
bool hasGenerated = false;
|
|
bool hasDeleted = false;
|
|
std::set<std::string> aSeen;
|
|
const auto appendRecord = [&](const char* theRelation, const TopAbs_ShapeEnum theKind, const std::size_t theSourceIndex, const int theResultIndex) {
|
|
const std::string aKey = std::string(theRelation) + ":" + kindName(theKind) + ":" + std::to_string(theSourceIndex) + ":" + std::to_string(theResultIndex);
|
|
if (!aSeen.insert(aKey).second) return;
|
|
emscripten::val aRecord = emscripten::val::object();
|
|
aRecord.set("relation", theRelation); aRecord.set("source", "object"); aRecord.set("kind", kindName(theKind));
|
|
aRecord.set("sourceIndex", static_cast<int>(theSourceIndex)); aRecord.set("resultIndex", theResultIndex);
|
|
aRecords.set(aRecordIndex++, aRecord);
|
|
hasModified = hasModified || std::string(theRelation) == "modified";
|
|
hasGenerated = hasGenerated || std::string(theRelation) == "generated";
|
|
hasDeleted = hasDeleted || std::string(theRelation) == "deleted";
|
|
};
|
|
for (std::size_t aKindIndex = 0; aKindIndex < 3; ++aKindIndex)
|
|
{
|
|
for (std::size_t aSourceIndex = 0; aSourceIndex < aBaseInstance.sourceShapes[aKindIndex].size(); ++aSourceIndex)
|
|
{
|
|
bool hasResult = false;
|
|
bool allDeleted = true;
|
|
for (std::size_t anInstanceIndex = 0; anInstanceIndex < anInstances.size(); ++anInstanceIndex)
|
|
{
|
|
const TopoDS_Shape& anInput = anInstances[anInstanceIndex].sourceShapes[aKindIndex][aSourceIndex];
|
|
const char* aRelation = anInstanceIndex == 0 ? "modified" : "generated";
|
|
const auto& aModified = anAlgorithm.Modified(anInput);
|
|
for (NCollection_List<TopoDS_Shape>::Iterator anIterator(aModified); anIterator.More(); anIterator.Next())
|
|
{
|
|
const int anOutputIndex = resultIndex(anIterator.Value(), aResult, aKinds[aKindIndex]);
|
|
if (anOutputIndex >= 0) { appendRecord(aRelation, aKinds[aKindIndex], aSourceIndex, anOutputIndex); hasResult = true; }
|
|
}
|
|
const int aDirectIndex = resultIndex(anInput, aResult, aKinds[aKindIndex]);
|
|
if (aDirectIndex >= 0) { appendRecord(aRelation, aKinds[aKindIndex], aSourceIndex, aDirectIndex); hasResult = true; }
|
|
allDeleted = allDeleted && anAlgorithm.IsDeleted(anInput);
|
|
}
|
|
if (!hasResult && allDeleted) appendRecord("deleted", aKinds[aKindIndex], aSourceIndex, -1);
|
|
}
|
|
}
|
|
if (aRecordIndex == 0) throw std::runtime_error("OCCT ordered MultiTransform produced no source history.");
|
|
emscripten::val aResponse = emscripten::val::object();
|
|
aResponse.set("provider", "occt-native"); aResponse.set("occtVersion", OCC_VERSION_COMPLETE); aResponse.set("result", aResult); aResponse.set("resultStep", writeStep(aResult)); aResponse.set("resultBrep", writeBrep(aResult));
|
|
aResponse.set("records", aRecords); aResponse.set("hasModified", hasModified); aResponse.set("hasGenerated", hasGenerated); aResponse.set("hasDeleted", hasDeleted); aResponse.set("summary", shapeSummary(aResult));
|
|
return aResponse;
|
|
}
|
|
|
|
emscripten::val holeHistoryFromStep(const std::string& theBaseStep,
|
|
const double theRadius,
|
|
const double theDepth,
|
|
const double thePositionX,
|
|
const double thePositionY,
|
|
const double thePositionZ,
|
|
const double theDirectionX,
|
|
const double theDirectionY,
|
|
const double theDirectionZ)
|
|
{
|
|
if (!std::isfinite(theRadius) || theRadius <= 0.0) throw std::invalid_argument("Hole radius must be finite and positive.");
|
|
if (!std::isfinite(theDepth) || theDepth <= 0.0) throw std::invalid_argument("Hole depth must be finite and positive.");
|
|
const gp_Vec aDirection(theDirectionX, theDirectionY, theDirectionZ);
|
|
if (aDirection.Magnitude() <= gp::Resolution()) throw std::invalid_argument("Hole direction must be non-zero.");
|
|
const TopoDS_Shape aBase = readStep(theBaseStep);
|
|
const gp_Ax2 anAxis(gp_Pnt(thePositionX, thePositionY, thePositionZ), gp_Dir(theDirectionX, theDirectionY, theDirectionZ));
|
|
const TopoDS_Shape aTool = BRepPrimAPI_MakeCylinder(anAxis, theRadius, theDepth).Shape();
|
|
BRepAlgoAPI_Cut anAlgorithm(aBase, aTool);
|
|
return runBoolean(anAlgorithm, aBase, aTool);
|
|
}
|
|
}
|
|
|
|
EMSCRIPTEN_BINDINGS(bitbybit_occt_history)
|
|
{
|
|
emscripten::class_<TopoDS_Shape>("TopoDS_Shape")
|
|
.constructor<>()
|
|
.function("isNull", &TopoDS_Shape::IsNull)
|
|
.function("shapeType", &TopoDS_Shape::ShapeType)
|
|
.function("isSame", &TopoDS_Shape::IsSame);
|
|
emscripten::function("occtVersion", &occtVersion);
|
|
emscripten::function("makeBox", &makeBox);
|
|
emscripten::function("makeBoxPlaced", &makeBoxPlaced);
|
|
emscripten::function("makeCylinder", &makeCylinder);
|
|
emscripten::function("makeSphere", &makeSphere);
|
|
emscripten::function("makeCone", &makeCone);
|
|
emscripten::function("makeRectangleFace", &makeRectangleFace);
|
|
emscripten::function("makeRectangleFacePlaced", &makeRectangleFacePlaced);
|
|
emscripten::function("makeLineWire", &makeLineWire);
|
|
emscripten::function("shapeSummary", &shapeSummary);
|
|
emscripten::function("booleanHistory", &booleanHistory);
|
|
emscripten::function("booleanProbe", &booleanProbe);
|
|
emscripten::function("booleanResult", &booleanResult);
|
|
emscripten::function("shapeToStep", &writeStep);
|
|
emscripten::function("booleanHistoryFromStep", &booleanHistoryFromStep);
|
|
emscripten::function("rotateHistoryFromStep", &rotateHistoryFromStep);
|
|
emscripten::function("prismHistory", &prismHistory);
|
|
emscripten::function("prismHistoryFromStep", &prismHistoryFromStep);
|
|
emscripten::function("loftHistoryFromStep", &loftHistoryFromStep);
|
|
emscripten::function("pipeHistoryFromStep", &pipeHistoryFromStep);
|
|
emscripten::function("pocketHistory", &pocketHistory);
|
|
emscripten::function("pocketHistoryFromStep", &pocketHistoryFromStep);
|
|
emscripten::function("revolutionHistory", &revolutionHistory);
|
|
emscripten::function("revolutionHistoryFromStep", &revolutionHistoryFromStep);
|
|
emscripten::function("grooveHistory", &grooveHistory);
|
|
emscripten::function("grooveHistoryFromStep", &grooveHistoryFromStep);
|
|
emscripten::function("filletHistoryFromStep", &filletHistoryFromStep);
|
|
emscripten::function("chamferHistoryFromStep", &chamferHistoryFromStep);
|
|
emscripten::function("draftHistoryFromStep", &draftHistoryFromStep);
|
|
emscripten::function("thicknessHistoryFromStep", &thicknessHistoryFromStep);
|
|
emscripten::function("linearPatternHistoryFromStep", &linearPatternHistoryFromStep);
|
|
emscripten::function("polarPatternHistoryFromStep", &polarPatternHistoryFromStep);
|
|
emscripten::function("mirroredHistoryFromStep", &mirroredHistoryFromStep);
|
|
emscripten::function("multiTransformHistoryFromStep", &multiTransformHistoryFromStep);
|
|
emscripten::function("holeHistoryFromStep", &holeHistoryFromStep);
|
|
}
|