#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 subShapes(const TopoDS_Shape& theShape, const TopAbs_ShapeEnum theKind) { std::vector 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); 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 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::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(aSourceIndex)); aRecord.set("resultIndex", anOutputIndex); aRecords.set(aRecordIndex++, aRecord); } const auto& aGenerated = theAlgorithm.Generated(anInput); for (NCollection_List::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(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(aSourceIndex)); 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("records", aRecords); aResponse.set("hasModified", theAlgorithm.HasModified()); aResponse.set("hasGenerated", theAlgorithm.HasGenerated()); aResponse.set("hasDeleted", theAlgorithm.HasDeleted()); return aResponse; } 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(); } 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."); } 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."); } 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(); } emscripten::val booleanHistoryFromStep(const std::string& theObjectStep, const std::string& theToolStep, const std::string& theOperation) { return booleanHistory(readStep(theObjectStep), readStep(theToolStep), theOperation); } } EMSCRIPTEN_BINDINGS(bitbybit_occt_history) { emscripten::class_("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("booleanHistory", &booleanHistory); emscripten::function("shapeToStep", &writeStep); emscripten::function("booleanHistoryFromStep", &booleanHistoryFromStep); }