10C Inc 2: feature tail
119 of the app's 138 symbols now resolve; only the I/O modules and the Inc 4 tail are left. New: GCE2d, GCPnts, BRepFilletAPI, BRepOffsetAPI, ShapeAnalysis, ShapeFix, ShapeUpgrade, BRepCheck, BRepTools, BRepLib, BRepExtrema, BRepClass3d, IntCurvesFace, plus TopTools_HSequenceOfShape. The fillet and chamfer builders derive BRepBuilderAPI_MakeShape, so their history comes from the base bound in Inc 1 — which is what lets the app read a blend's provenance the same way it reads a boolean's. The Inc 2 gate compares that history exactly, alongside the splitter's, which is what sketch-region attribution depends on. Two shapes of deviation, both commented where they are bound: - Trailing enum arguments are left off four constructors (fillet's ChFi3d_FilletShape, MakeThickSolidByJoin's mode/join pair, BRepExtrema's Extrema flags, and Inc 1's GeomAPI_ProjectPointOnSurf). An unregistered enum cannot serve as a default argument — nanobind converts defaults at binding time, so it fails the whole module's import with std::bad_cast. The app never overrides any of them, so OCCT's own defaults apply and behaviour is unchanged. - ShapeAnalysis_FreeBounds.ConnectEdgesToWires reassigns a Handle& out parameter, which the caster cannot honour because it hands C++ a copy of the handle. The lambda splices the result into the sequence the caller passed, so the pass-empty-then-read shape the app uses still works. This is the one place upstream's holder semantics and ours differ observably. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6
This commit is contained in:
@@ -60,6 +60,11 @@ target_link_libraries(_OCP PRIVATE
|
|||||||
TKPrim # BRepPrimAPI
|
TKPrim # BRepPrimAPI
|
||||||
TKBO # BRepAlgoAPI, BOPAlgo
|
TKBO # BRepAlgoAPI, BOPAlgo
|
||||||
TKMesh # BRepMesh
|
TKMesh # BRepMesh
|
||||||
|
# Inc 2
|
||||||
|
TKFillet # BRepFilletAPI
|
||||||
|
TKOffset # BRepOffsetAPI
|
||||||
|
TKShHealing # ShapeFix, ShapeAnalysis, ShapeUpgrade
|
||||||
|
TKBool # BRepAlgo helpers pulled in by the above
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(_OCP PRIVATE
|
target_compile_definitions(_OCP PRIVATE
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ name = "n3xd-ocp"
|
|||||||
# .devN suffix: the registry never allows republishing a version, and its
|
# .devN suffix: the registry never allows republishing a version, and its
|
||||||
# cleanup rule collects only the dev ones. CMake asserts the prefix matches
|
# cleanup rule collects only the dev ones. CMake asserts the prefix matches
|
||||||
# the OCCT it found.
|
# the OCCT it found.
|
||||||
version = "7.9.3.1.dev2"
|
version = "7.9.3.1.dev3"
|
||||||
description = "nanobind bindings for the OpenCASCADE geometry kernel (drop-in OCP)"
|
description = "nanobind bindings for the OpenCASCADE geometry kernel (drop-in OCP)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
29
src/core.cpp
29
src/core.cpp
@@ -52,6 +52,20 @@ void register_BRepAlgoAPI(nb::module_ &);
|
|||||||
void register_BRepPrimAPI(nb::module_ &);
|
void register_BRepPrimAPI(nb::module_ &);
|
||||||
void register_GC(nb::module_ &);
|
void register_GC(nb::module_ &);
|
||||||
void register_BRepMesh(nb::module_ &);
|
void register_BRepMesh(nb::module_ &);
|
||||||
|
// Inc 2 — feature tail
|
||||||
|
void register_GCE2d(nb::module_ &);
|
||||||
|
void register_GCPnts(nb::module_ &);
|
||||||
|
void register_BRepFilletAPI(nb::module_ &);
|
||||||
|
void register_BRepOffsetAPI(nb::module_ &);
|
||||||
|
void register_ShapeAnalysis(nb::module_ &);
|
||||||
|
void register_ShapeFix(nb::module_ &);
|
||||||
|
void register_ShapeUpgrade(nb::module_ &);
|
||||||
|
void register_BRepCheck(nb::module_ &);
|
||||||
|
void register_BRepTools(nb::module_ &);
|
||||||
|
void register_BRepLib(nb::module_ &);
|
||||||
|
void register_BRepExtrema(nb::module_ &);
|
||||||
|
void register_BRepClass3d(nb::module_ &);
|
||||||
|
void register_IntCurvesFace(nb::module_ &);
|
||||||
void register_ext(nb::module_ &);
|
void register_ext(nb::module_ &);
|
||||||
|
|
||||||
NB_MODULE(_OCP, m) {
|
NB_MODULE(_OCP, m) {
|
||||||
@@ -95,6 +109,21 @@ NB_MODULE(_OCP, m) {
|
|||||||
register_GC(m);
|
register_GC(m);
|
||||||
register_BRepMesh(m);
|
register_BRepMesh(m);
|
||||||
|
|
||||||
|
// Inc 2.
|
||||||
|
register_GCE2d(m);
|
||||||
|
register_GCPnts(m);
|
||||||
|
register_BRepFilletAPI(m);
|
||||||
|
register_BRepOffsetAPI(m);
|
||||||
|
register_ShapeAnalysis(m);
|
||||||
|
register_ShapeFix(m);
|
||||||
|
register_ShapeUpgrade(m);
|
||||||
|
register_BRepCheck(m);
|
||||||
|
register_BRepTools(m);
|
||||||
|
register_BRepLib(m);
|
||||||
|
register_BRepExtrema(m);
|
||||||
|
register_BRepClass3d(m);
|
||||||
|
register_IntCurvesFace(m);
|
||||||
|
|
||||||
register_ext(m);
|
register_ext(m);
|
||||||
|
|
||||||
m.attr("__all_modules__") = ocp_module_registry();
|
m.attr("__all_modules__") = ocp_module_registry();
|
||||||
|
|||||||
89
src/modules/mod_BRepCheck.cpp
Normal file
89
src/modules/mod_BRepCheck.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
OCP.BRepCheck — shape validity.
|
||||||
|
|
||||||
|
The analyzer runs on every preview and after every boolean. Its
|
||||||
|
constructor does the work, so that is what releases the GIL; the per-face
|
||||||
|
Result() lookups afterwards are cheap.
|
||||||
|
|
||||||
|
Only the two status values the app tests for are worth calling out —
|
||||||
|
it names the self-intersecting/intersecting-wire pair when deciding whether
|
||||||
|
an imported shape is repairable — but the whole enum is bound, since it is
|
||||||
|
what Result() reports through.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <BRepCheck_Analyzer.hxx>
|
||||||
|
#include <BRepCheck_Result.hxx>
|
||||||
|
#include <BRepCheck_Status.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
|
||||||
|
void register_BRepCheck(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "BRepCheck");
|
||||||
|
|
||||||
|
nb::enum_<BRepCheck_Status>(m, "BRepCheck_Status", nb::is_arithmetic())
|
||||||
|
.value("BRepCheck_NoError", BRepCheck_NoError)
|
||||||
|
.value("BRepCheck_InvalidPointOnCurve", BRepCheck_InvalidPointOnCurve)
|
||||||
|
.value("BRepCheck_InvalidPointOnCurveOnSurface",
|
||||||
|
BRepCheck_InvalidPointOnCurveOnSurface)
|
||||||
|
.value("BRepCheck_InvalidPointOnSurface", BRepCheck_InvalidPointOnSurface)
|
||||||
|
.value("BRepCheck_No3DCurve", BRepCheck_No3DCurve)
|
||||||
|
.value("BRepCheck_Multiple3DCurve", BRepCheck_Multiple3DCurve)
|
||||||
|
.value("BRepCheck_Invalid3DCurve", BRepCheck_Invalid3DCurve)
|
||||||
|
.value("BRepCheck_NoCurveOnSurface", BRepCheck_NoCurveOnSurface)
|
||||||
|
.value("BRepCheck_InvalidCurveOnSurface", BRepCheck_InvalidCurveOnSurface)
|
||||||
|
.value("BRepCheck_InvalidCurveOnClosedSurface",
|
||||||
|
BRepCheck_InvalidCurveOnClosedSurface)
|
||||||
|
.value("BRepCheck_InvalidSameRangeFlag", BRepCheck_InvalidSameRangeFlag)
|
||||||
|
.value("BRepCheck_InvalidSameParameterFlag",
|
||||||
|
BRepCheck_InvalidSameParameterFlag)
|
||||||
|
.value("BRepCheck_InvalidDegeneratedFlag", BRepCheck_InvalidDegeneratedFlag)
|
||||||
|
.value("BRepCheck_FreeEdge", BRepCheck_FreeEdge)
|
||||||
|
.value("BRepCheck_InvalidMultiConnexity", BRepCheck_InvalidMultiConnexity)
|
||||||
|
.value("BRepCheck_InvalidRange", BRepCheck_InvalidRange)
|
||||||
|
.value("BRepCheck_EmptyWire", BRepCheck_EmptyWire)
|
||||||
|
.value("BRepCheck_RedundantEdge", BRepCheck_RedundantEdge)
|
||||||
|
.value("BRepCheck_SelfIntersectingWire", BRepCheck_SelfIntersectingWire)
|
||||||
|
.value("BRepCheck_NoSurface", BRepCheck_NoSurface)
|
||||||
|
.value("BRepCheck_InvalidWire", BRepCheck_InvalidWire)
|
||||||
|
.value("BRepCheck_RedundantWire", BRepCheck_RedundantWire)
|
||||||
|
.value("BRepCheck_IntersectingWires", BRepCheck_IntersectingWires)
|
||||||
|
.value("BRepCheck_InvalidImbricationOfWires",
|
||||||
|
BRepCheck_InvalidImbricationOfWires)
|
||||||
|
.value("BRepCheck_EmptyShell", BRepCheck_EmptyShell)
|
||||||
|
.value("BRepCheck_RedundantFace", BRepCheck_RedundantFace)
|
||||||
|
.value("BRepCheck_InvalidImbricationOfShells",
|
||||||
|
BRepCheck_InvalidImbricationOfShells)
|
||||||
|
.value("BRepCheck_UnorientableShape", BRepCheck_UnorientableShape)
|
||||||
|
.value("BRepCheck_NotClosed", BRepCheck_NotClosed)
|
||||||
|
.value("BRepCheck_NotConnected", BRepCheck_NotConnected)
|
||||||
|
.value("BRepCheck_SubshapeNotInShape", BRepCheck_SubshapeNotInShape)
|
||||||
|
.value("BRepCheck_BadOrientation", BRepCheck_BadOrientation)
|
||||||
|
.value("BRepCheck_BadOrientationOfSubshape",
|
||||||
|
BRepCheck_BadOrientationOfSubshape)
|
||||||
|
.value("BRepCheck_InvalidPolygonOnTriangulation",
|
||||||
|
BRepCheck_InvalidPolygonOnTriangulation)
|
||||||
|
.value("BRepCheck_InvalidToleranceValue", BRepCheck_InvalidToleranceValue)
|
||||||
|
.value("BRepCheck_EnclosedRegion", BRepCheck_EnclosedRegion)
|
||||||
|
.value("BRepCheck_CheckFail", BRepCheck_CheckFail)
|
||||||
|
.export_values();
|
||||||
|
|
||||||
|
nb::class_<BRepCheck_Result, Standard_Transient>(m, "BRepCheck_Result")
|
||||||
|
.def("Status", &BRepCheck_Result::Status, OCP_RETURN_COPY)
|
||||||
|
.def("IsMinimum", &BRepCheck_Result::IsMinimum);
|
||||||
|
|
||||||
|
nb::class_<BRepCheck_Analyzer>(m, "BRepCheck_Analyzer")
|
||||||
|
.def(nb::init<const TopoDS_Shape &, const Standard_Boolean,
|
||||||
|
const Standard_Boolean, const Standard_Boolean>(),
|
||||||
|
"S"_a, "GeomControls"_a = Standard_True,
|
||||||
|
"theIsParallel"_a = Standard_False,
|
||||||
|
"theIsExact"_a = Standard_False, OCP_NOGIL)
|
||||||
|
.def("IsValid", nb::overload_cast<>(&BRepCheck_Analyzer::IsValid,
|
||||||
|
nb::const_))
|
||||||
|
.def("IsValid",
|
||||||
|
nb::overload_cast<const TopoDS_Shape &>(&BRepCheck_Analyzer::IsValid,
|
||||||
|
nb::const_),
|
||||||
|
"S"_a)
|
||||||
|
.def("Result", &BRepCheck_Analyzer::Result, "theSubS"_a);
|
||||||
|
}
|
||||||
31
src/modules/mod_BRepClass3d.cpp
Normal file
31
src/modules/mod_BRepClass3d.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
OCP.BRepClass3d — point-in-solid classification.
|
||||||
|
|
||||||
|
Answers "is this point inside the material", which the app uses to decide
|
||||||
|
which side of a face to grow a feature from and to verify that a thread
|
||||||
|
groove did not gouge its shoulder. The three-argument constructor
|
||||||
|
classifies immediately; the default one needs a separate Perform.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <BRepClass3d_SolidClassifier.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
#include <gp_Pnt.hxx>
|
||||||
|
|
||||||
|
void register_BRepClass3d(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "BRepClass3d");
|
||||||
|
|
||||||
|
nb::class_<BRepClass3d_SolidClassifier>(m, "BRepClass3d_SolidClassifier")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def(nb::init<const TopoDS_Shape &>(), "S"_a, OCP_NOGIL)
|
||||||
|
.def(nb::init<const TopoDS_Shape &, const gp_Pnt &,
|
||||||
|
const Standard_Real>(),
|
||||||
|
"S"_a, "P"_a, "Tol"_a, OCP_NOGIL)
|
||||||
|
.def("Load", &BRepClass3d_SolidClassifier::Load, "S"_a, OCP_NOGIL)
|
||||||
|
.def("Perform", &BRepClass3d_SolidClassifier::Perform, "P"_a, "Tol"_a,
|
||||||
|
OCP_NOGIL)
|
||||||
|
.def("State", &BRepClass3d_SolidClassifier::State)
|
||||||
|
.def("IsOnAFace", &BRepClass3d_SolidClassifier::IsOnAFace);
|
||||||
|
}
|
||||||
34
src/modules/mod_BRepExtrema.cpp
Normal file
34
src/modules/mod_BRepExtrema.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
OCP.BRepExtrema — minimum distance between two shapes.
|
||||||
|
|
||||||
|
Used by the measurement tools and the interference check. The constructor
|
||||||
|
performs the search, so it releases the GIL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <BRepExtrema_DistShapeShape.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
#include <gp_Pnt.hxx>
|
||||||
|
|
||||||
|
void register_BRepExtrema(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "BRepExtrema");
|
||||||
|
|
||||||
|
nb::class_<BRepExtrema_DistShapeShape>(m, "BRepExtrema_DistShapeShape")
|
||||||
|
.def(nb::init<>())
|
||||||
|
// Without the Extrema flag/algo overload: unregistered enums cannot
|
||||||
|
// serve as default arguments, and the app takes OCCT's defaults.
|
||||||
|
.def(nb::init<const TopoDS_Shape &, const TopoDS_Shape &>(), "Shape1"_a,
|
||||||
|
"Shape2"_a, OCP_NOGIL)
|
||||||
|
.def(
|
||||||
|
"Perform", [](BRepExtrema_DistShapeShape &self) { self.Perform(); },
|
||||||
|
OCP_NOGIL)
|
||||||
|
.def("IsDone", &BRepExtrema_DistShapeShape::IsDone)
|
||||||
|
.def("NbSolution", &BRepExtrema_DistShapeShape::NbSolution)
|
||||||
|
.def("Value", &BRepExtrema_DistShapeShape::Value)
|
||||||
|
.def("PointOnShape1", &BRepExtrema_DistShapeShape::PointOnShape1, "N"_a,
|
||||||
|
OCP_RETURN_COPY)
|
||||||
|
.def("PointOnShape2", &BRepExtrema_DistShapeShape::PointOnShape2, "N"_a,
|
||||||
|
OCP_RETURN_COPY);
|
||||||
|
}
|
||||||
66
src/modules/mod_BRepFilletAPI.cpp
Normal file
66
src/modules/mod_BRepFilletAPI.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
OCP.BRepFilletAPI — fillets and chamfers.
|
||||||
|
|
||||||
|
Both derive BRepBuilderAPI_MakeShape, so Build/Shape and — importantly —
|
||||||
|
the Generated/Modified/IsDeleted history come from there. The app reads
|
||||||
|
that history to attribute the blend faces back to the feature that made
|
||||||
|
them, the same way it reads a boolean's.
|
||||||
|
|
||||||
|
These are the two operations the app runs in an isolated subprocess, since
|
||||||
|
OCCT can hard-fault on a degenerate blend; nothing here changes that, but
|
||||||
|
releasing the GIL on Build() is what lets the pool overlap work.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <BRepBuilderAPI_MakeShape.hxx>
|
||||||
|
#include <BRepFilletAPI_MakeChamfer.hxx>
|
||||||
|
#include <BRepFilletAPI_MakeFillet.hxx>
|
||||||
|
#include <TopoDS_Edge.hxx>
|
||||||
|
#include <TopoDS_Face.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
|
||||||
|
void register_BRepFilletAPI(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "BRepFilletAPI");
|
||||||
|
|
||||||
|
nb::class_<BRepFilletAPI_MakeFillet, BRepBuilderAPI_MakeShape>(
|
||||||
|
m, "BRepFilletAPI_MakeFillet")
|
||||||
|
// The ChFi3d_FilletShape overload is left out: it exists only to
|
||||||
|
// carry a default the app never overrides, and binding an unregistered
|
||||||
|
// enum as a default argument fails at module-init time.
|
||||||
|
.def(nb::init<const TopoDS_Shape &>(), "S"_a)
|
||||||
|
.def("Add",
|
||||||
|
nb::overload_cast<const Standard_Real, const TopoDS_Edge &>(
|
||||||
|
&BRepFilletAPI_MakeFillet::Add),
|
||||||
|
"Radius"_a, "E"_a)
|
||||||
|
.def("Add",
|
||||||
|
nb::overload_cast<const Standard_Real, const Standard_Real,
|
||||||
|
const TopoDS_Edge &>(
|
||||||
|
&BRepFilletAPI_MakeFillet::Add),
|
||||||
|
"R1"_a, "R2"_a, "E"_a)
|
||||||
|
.def("NbContours", &BRepFilletAPI_MakeFillet::NbContours)
|
||||||
|
.def("Reset", &BRepFilletAPI_MakeFillet::Reset);
|
||||||
|
|
||||||
|
nb::class_<BRepFilletAPI_MakeChamfer, BRepBuilderAPI_MakeShape>(
|
||||||
|
m, "BRepFilletAPI_MakeChamfer")
|
||||||
|
.def(nb::init<const TopoDS_Shape &>(), "S"_a)
|
||||||
|
.def("Add",
|
||||||
|
nb::overload_cast<const TopoDS_Edge &>(
|
||||||
|
&BRepFilletAPI_MakeChamfer::Add),
|
||||||
|
"E"_a)
|
||||||
|
.def("Add",
|
||||||
|
nb::overload_cast<const Standard_Real, const TopoDS_Edge &>(
|
||||||
|
&BRepFilletAPI_MakeChamfer::Add),
|
||||||
|
"Dis"_a, "E"_a)
|
||||||
|
.def("Add",
|
||||||
|
nb::overload_cast<const Standard_Real, const Standard_Real,
|
||||||
|
const TopoDS_Edge &, const TopoDS_Face &>(
|
||||||
|
&BRepFilletAPI_MakeChamfer::Add),
|
||||||
|
"Dis1"_a, "Dis2"_a, "E"_a, "F"_a)
|
||||||
|
// Distance-and-angle, which is how the app words an asymmetric chamfer.
|
||||||
|
.def("AddDA", &BRepFilletAPI_MakeChamfer::AddDA, "Dis"_a, "Angle"_a,
|
||||||
|
"E"_a, "F"_a)
|
||||||
|
.def("NbContours", &BRepFilletAPI_MakeChamfer::NbContours)
|
||||||
|
.def("Reset", &BRepFilletAPI_MakeChamfer::Reset);
|
||||||
|
}
|
||||||
31
src/modules/mod_BRepLib.cpp
Normal file
31
src/modules/mod_BRepLib.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
OCP.BRepLib — repairs applied to shapes built edge by edge.
|
||||||
|
|
||||||
|
BuildCurves3d recovers the 3D curve of an edge that only carries a pcurve,
|
||||||
|
which is how the thread's helical edges (laid out in the cylinder's UV
|
||||||
|
space) become real geometry. OrientClosedSolid fixes a solid built from a
|
||||||
|
sewn shell whose orientation came out inward.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <BRepLib.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
#include <TopoDS_Solid.hxx>
|
||||||
|
|
||||||
|
void register_BRepLib(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "BRepLib");
|
||||||
|
|
||||||
|
nb::class_<BRepLib> cls(m, "BRepLib");
|
||||||
|
|
||||||
|
OCP_DEF_S(
|
||||||
|
cls, "BuildCurves3d",
|
||||||
|
[](const TopoDS_Shape &S) { return BRepLib::BuildCurves3d(S); }, "S"_a,
|
||||||
|
OCP_NOGIL);
|
||||||
|
|
||||||
|
OCP_DEF_S(
|
||||||
|
cls, "OrientClosedSolid",
|
||||||
|
[](TopoDS_Solid &solid) { return BRepLib::OrientClosedSolid(solid); },
|
||||||
|
"solid"_a, OCP_NOGIL);
|
||||||
|
}
|
||||||
86
src/modules/mod_BRepOffsetAPI.cpp
Normal file
86
src/modules/mod_BRepOffsetAPI.cpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
OCP.BRepOffsetAPI — sweeps, lofts, shells and drafts.
|
||||||
|
|
||||||
|
All five derive BRepBuilderAPI_MakeShape, so Build/Shape/history come from
|
||||||
|
there. MakeThickSolid is the one with an unusual shape: it is default
|
||||||
|
constructed and then told what to do by MakeThickSolidBySimple (a sheet
|
||||||
|
thickened into a wall) or MakeThickSolidByJoin (a solid hollowed with
|
||||||
|
faces removed), each of which does the work — so those get the GIL release
|
||||||
|
rather than Build().
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <BRepBuilderAPI_MakeShape.hxx>
|
||||||
|
#include <BRepOffsetAPI_DraftAngle.hxx>
|
||||||
|
#include <BRepOffsetAPI_MakePipe.hxx>
|
||||||
|
#include <BRepOffsetAPI_MakePipeShell.hxx>
|
||||||
|
#include <BRepOffsetAPI_MakeThickSolid.hxx>
|
||||||
|
#include <BRepOffsetAPI_ThruSections.hxx>
|
||||||
|
#include <TopTools_ListOfShape.hxx>
|
||||||
|
#include <TopoDS_Face.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
#include <TopoDS_Wire.hxx>
|
||||||
|
#include <gp_Dir.hxx>
|
||||||
|
#include <gp_Pln.hxx>
|
||||||
|
|
||||||
|
void register_BRepOffsetAPI(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "BRepOffsetAPI");
|
||||||
|
|
||||||
|
nb::class_<BRepOffsetAPI_ThruSections, BRepBuilderAPI_MakeShape>(
|
||||||
|
m, "BRepOffsetAPI_ThruSections")
|
||||||
|
.def(nb::init<const Standard_Boolean, const Standard_Boolean,
|
||||||
|
const Standard_Real>(),
|
||||||
|
"isSolid"_a = Standard_False, "ruled"_a = Standard_False,
|
||||||
|
"pres3d"_a = 1.0e-06)
|
||||||
|
.def("AddWire", &BRepOffsetAPI_ThruSections::AddWire, "wire"_a)
|
||||||
|
.def("CheckCompatibility",
|
||||||
|
&BRepOffsetAPI_ThruSections::CheckCompatibility, "check"_a = true);
|
||||||
|
|
||||||
|
nb::class_<BRepOffsetAPI_MakePipe, BRepBuilderAPI_MakeShape>(
|
||||||
|
m, "BRepOffsetAPI_MakePipe")
|
||||||
|
.def(nb::init<const TopoDS_Wire &, const TopoDS_Shape &>(), "Spine"_a,
|
||||||
|
"Profile"_a, OCP_NOGIL);
|
||||||
|
|
||||||
|
nb::class_<BRepOffsetAPI_MakePipeShell, BRepBuilderAPI_MakeShape>(
|
||||||
|
m, "BRepOffsetAPI_MakePipeShell")
|
||||||
|
.def(nb::init<const TopoDS_Wire &>(), "Spine"_a)
|
||||||
|
.def("SetMode",
|
||||||
|
nb::overload_cast<const Standard_Boolean>(
|
||||||
|
&BRepOffsetAPI_MakePipeShell::SetMode),
|
||||||
|
"IsFrenet"_a = Standard_False)
|
||||||
|
.def("Add",
|
||||||
|
nb::overload_cast<const TopoDS_Shape &, const Standard_Boolean,
|
||||||
|
const Standard_Boolean>(
|
||||||
|
&BRepOffsetAPI_MakePipeShell::Add),
|
||||||
|
"Profile"_a, "WithContact"_a = Standard_False,
|
||||||
|
"WithCorrection"_a = Standard_False)
|
||||||
|
.def("MakeSolid", &BRepOffsetAPI_MakePipeShell::MakeSolid, OCP_NOGIL);
|
||||||
|
|
||||||
|
nb::class_<BRepOffsetAPI_MakeThickSolid, BRepBuilderAPI_MakeShape>(
|
||||||
|
m, "BRepOffsetAPI_MakeThickSolid")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def("MakeThickSolidBySimple",
|
||||||
|
&BRepOffsetAPI_MakeThickSolid::MakeThickSolidBySimple, "theS"_a,
|
||||||
|
"theOffsetValue"_a, OCP_NOGIL)
|
||||||
|
// The trailing mode/join arguments are left off: they are enums the
|
||||||
|
// app never overrides, and an unregistered enum used as a default
|
||||||
|
// argument fails at module-init time. OCCT's own defaults apply.
|
||||||
|
.def(
|
||||||
|
"MakeThickSolidByJoin",
|
||||||
|
[](BRepOffsetAPI_MakeThickSolid &self, const TopoDS_Shape &S,
|
||||||
|
const TopTools_ListOfShape &ClosingFaces,
|
||||||
|
const Standard_Real Offset, const Standard_Real Tol) {
|
||||||
|
self.MakeThickSolidByJoin(S, ClosingFaces, Offset, Tol);
|
||||||
|
},
|
||||||
|
"S"_a, "ClosingFaces"_a, "Offset"_a, "Tol"_a, OCP_NOGIL);
|
||||||
|
|
||||||
|
nb::class_<BRepOffsetAPI_DraftAngle, BRepBuilderAPI_MakeShape>(
|
||||||
|
m, "BRepOffsetAPI_DraftAngle")
|
||||||
|
.def(nb::init<const TopoDS_Shape &>(), "S"_a)
|
||||||
|
.def("Add", &BRepOffsetAPI_DraftAngle::Add, "F"_a, "D"_a, "Angle"_a,
|
||||||
|
"NeutralPlane"_a, "Flag"_a = Standard_True)
|
||||||
|
.def("AddDone", &BRepOffsetAPI_DraftAngle::AddDone)
|
||||||
|
.def("Remove", &BRepOffsetAPI_DraftAngle::Remove, "F"_a);
|
||||||
|
}
|
||||||
70
src/modules/mod_BRepTools.cpp
Normal file
70
src/modules/mod_BRepTools.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
OCP.BRepTools — assorted topology utilities.
|
||||||
|
|
||||||
|
Clean drops an existing triangulation, which the tessellation path does
|
||||||
|
before re-meshing at a different deflection (OCCT keeps whatever mesh a
|
||||||
|
shape already carries, so without this the second pass is a no-op).
|
||||||
|
OuterWire and UVBounds are queries; WireExplorer walks a wire's edges in
|
||||||
|
connection order rather than map order, which is what wire-following code
|
||||||
|
needs.
|
||||||
|
|
||||||
|
UVBounds reports four scalar out-parameters, so it returns a tuple —
|
||||||
|
upstream does the same, and `umin, umax, vmin, vmax = ...` is how the app
|
||||||
|
calls it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <nanobind/stl/tuple.h>
|
||||||
|
|
||||||
|
#include <BRepTools.hxx>
|
||||||
|
#include <BRepTools_WireExplorer.hxx>
|
||||||
|
#include <TopoDS_Face.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
#include <TopoDS_Wire.hxx>
|
||||||
|
|
||||||
|
void register_BRepTools(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "BRepTools");
|
||||||
|
|
||||||
|
nb::class_<BRepTools_WireExplorer>(m, "BRepTools_WireExplorer")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def(nb::init<const TopoDS_Wire &>(), "W"_a)
|
||||||
|
.def(nb::init<const TopoDS_Wire &, const TopoDS_Face &>(), "W"_a, "F"_a)
|
||||||
|
.def("Init", nb::overload_cast<const TopoDS_Wire &>(
|
||||||
|
&BRepTools_WireExplorer::Init),
|
||||||
|
"W"_a)
|
||||||
|
.def("More", &BRepTools_WireExplorer::More)
|
||||||
|
.def("Next", &BRepTools_WireExplorer::Next)
|
||||||
|
.def("Current", &BRepTools_WireExplorer::Current, OCP_RETURN_COPY)
|
||||||
|
.def("Orientation", &BRepTools_WireExplorer::Orientation)
|
||||||
|
.def("CurrentVertex", &BRepTools_WireExplorer::CurrentVertex,
|
||||||
|
OCP_RETURN_COPY)
|
||||||
|
.def("Clear", &BRepTools_WireExplorer::Clear);
|
||||||
|
|
||||||
|
nb::class_<BRepTools> cls(m, "BRepTools");
|
||||||
|
|
||||||
|
OCP_DEF_S(
|
||||||
|
cls, "Clean",
|
||||||
|
[](const TopoDS_Shape &S, const Standard_Boolean force) {
|
||||||
|
BRepTools::Clean(S, force);
|
||||||
|
},
|
||||||
|
"S"_a, "force"_a = Standard_False, OCP_NOGIL);
|
||||||
|
|
||||||
|
OCP_DEF_S(
|
||||||
|
cls, "OuterWire",
|
||||||
|
[](const TopoDS_Face &F) { return BRepTools::OuterWire(F); }, "F"_a);
|
||||||
|
|
||||||
|
OCP_DEF_S(
|
||||||
|
cls, "UVBounds",
|
||||||
|
[](const TopoDS_Face &F) {
|
||||||
|
Standard_Real umin = 0.0, umax = 0.0, vmin = 0.0, vmax = 0.0;
|
||||||
|
BRepTools::UVBounds(F, umin, umax, vmin, vmax);
|
||||||
|
return std::make_tuple(umin, umax, vmin, vmax);
|
||||||
|
},
|
||||||
|
"F"_a);
|
||||||
|
|
||||||
|
OCP_DEF_S(
|
||||||
|
cls, "Update",
|
||||||
|
[](const TopoDS_Shape &S) { BRepTools::Update(S); }, "S"_a);
|
||||||
|
}
|
||||||
30
src/modules/mod_GCE2d.cpp
Normal file
30
src/modules/mod_GCE2d.cpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
OCP.GCE2d — 2D geometry constructors.
|
||||||
|
|
||||||
|
Used to lay curves out in a surface's UV space before lifting them onto it
|
||||||
|
with BRepBuilderAPI_MakeEdge: a thread's helix is a straight line in the
|
||||||
|
cylinder's parameter space, and curved text emboss draws its glyph
|
||||||
|
outlines the same way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
|
||||||
|
#include <GCE2d_MakeLine.hxx>
|
||||||
|
#include <GCE2d_MakeSegment.hxx>
|
||||||
|
#include <gp_Dir2d.hxx>
|
||||||
|
#include <gp_Pnt2d.hxx>
|
||||||
|
|
||||||
|
void register_GCE2d(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "GCE2d");
|
||||||
|
|
||||||
|
nb::class_<GCE2d_MakeLine>(m, "GCE2d_MakeLine")
|
||||||
|
.def(nb::init<const gp_Pnt2d &, const gp_Dir2d &>(), "P"_a, "V"_a)
|
||||||
|
.def(nb::init<const gp_Pnt2d &, const gp_Pnt2d &>(), "P1"_a, "P2"_a)
|
||||||
|
.def("IsDone", &GCE2d_MakeLine::IsDone)
|
||||||
|
.def("Value", &GCE2d_MakeLine::Value);
|
||||||
|
|
||||||
|
nb::class_<GCE2d_MakeSegment>(m, "GCE2d_MakeSegment")
|
||||||
|
.def(nb::init<const gp_Pnt2d &, const gp_Pnt2d &>(), "P1"_a, "P2"_a)
|
||||||
|
.def("IsDone", &GCE2d_MakeSegment::IsDone)
|
||||||
|
.def("Value", &GCE2d_MakeSegment::Value);
|
||||||
|
}
|
||||||
61
src/modules/mod_GCPnts.cpp
Normal file
61
src/modules/mod_GCPnts.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
OCP.GCPnts — curve discretisation.
|
||||||
|
|
||||||
|
TangentialDeflection is what turns every model edge into the polyline the
|
||||||
|
viewport draws, so it runs once per edge on every tessellation; AbscissaPoint
|
||||||
|
measures arc length for the edge anchors. Both take an Adaptor3d_Curve,
|
||||||
|
whose methods are bound on the base in mod_Adaptor3d.cpp.
|
||||||
|
|
||||||
|
The constructors compute — there is no deferred form — so they bind as
|
||||||
|
stock does.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <Adaptor3d_Curve.hxx>
|
||||||
|
#include <GCPnts_AbscissaPoint.hxx>
|
||||||
|
#include <GCPnts_TangentialDeflection.hxx>
|
||||||
|
#include <GCPnts_UniformDeflection.hxx>
|
||||||
|
#include <gp_Pnt.hxx>
|
||||||
|
|
||||||
|
void register_GCPnts(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "GCPnts");
|
||||||
|
|
||||||
|
nb::class_<GCPnts_TangentialDeflection>(m, "GCPnts_TangentialDeflection")
|
||||||
|
.def(nb::init<const Adaptor3d_Curve &, const Standard_Real,
|
||||||
|
const Standard_Real, const Standard_Integer,
|
||||||
|
const Standard_Real, const Standard_Real>(),
|
||||||
|
"C"_a, "AngularDeflection"_a, "CurvatureDeflection"_a,
|
||||||
|
"MinimumOfPoints"_a = 2, "UTol"_a = 1.0e-9,
|
||||||
|
"theMinLen"_a = 1.0e-7, OCP_NOGIL)
|
||||||
|
.def("NbPoints", &GCPnts_TangentialDeflection::NbPoints)
|
||||||
|
.def("Value", &GCPnts_TangentialDeflection::Value, "I"_a,
|
||||||
|
OCP_RETURN_COPY)
|
||||||
|
.def("Parameter", &GCPnts_TangentialDeflection::Parameter, "I"_a);
|
||||||
|
|
||||||
|
nb::class_<GCPnts_UniformDeflection>(m, "GCPnts_UniformDeflection")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def(nb::init<const Adaptor3d_Curve &, const Standard_Real,
|
||||||
|
const Standard_Boolean>(),
|
||||||
|
"C"_a, "Deflection"_a, "WithControl"_a = Standard_True, OCP_NOGIL)
|
||||||
|
.def("IsDone", &GCPnts_UniformDeflection::IsDone)
|
||||||
|
.def("NbPoints", &GCPnts_UniformDeflection::NbPoints)
|
||||||
|
.def("Value", &GCPnts_UniformDeflection::Value, "Index"_a,
|
||||||
|
OCP_RETURN_COPY)
|
||||||
|
.def("Parameter", &GCPnts_UniformDeflection::Parameter, "Index"_a);
|
||||||
|
|
||||||
|
nb::class_<GCPnts_AbscissaPoint> abscissa(m, "GCPnts_AbscissaPoint");
|
||||||
|
abscissa.def(nb::init<>());
|
||||||
|
OCP_DEF_S(
|
||||||
|
abscissa, "Length",
|
||||||
|
[](const Adaptor3d_Curve &C) { return GCPnts_AbscissaPoint::Length(C); },
|
||||||
|
"C"_a, OCP_NOGIL);
|
||||||
|
OCP_DEF_S(
|
||||||
|
abscissa, "Length",
|
||||||
|
[](const Adaptor3d_Curve &C, const Standard_Real U1,
|
||||||
|
const Standard_Real U2) {
|
||||||
|
return GCPnts_AbscissaPoint::Length(C, U1, U2);
|
||||||
|
},
|
||||||
|
"C"_a, "U1"_a, "U2"_a, OCP_NOGIL);
|
||||||
|
}
|
||||||
42
src/modules/mod_IntCurvesFace.cpp
Normal file
42
src/modules/mod_IntCurvesFace.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
OCP.IntCurvesFace — ray casting against a shape.
|
||||||
|
|
||||||
|
The surface-pattern feature fires a ray from each sample point along the
|
||||||
|
inverted normal to find the opposite wall, which is how it measures local
|
||||||
|
wall thickness. Load prepares the shape once; PerformNearest runs per ray
|
||||||
|
inside a Python loop, so both release the GIL but neither is a long call.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <IntCurvesFace_ShapeIntersector.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
#include <gp_Lin.hxx>
|
||||||
|
#include <gp_Pnt.hxx>
|
||||||
|
|
||||||
|
void register_IntCurvesFace(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "IntCurvesFace");
|
||||||
|
|
||||||
|
nb::class_<IntCurvesFace_ShapeIntersector>(m,
|
||||||
|
"IntCurvesFace_ShapeIntersector")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def("Load", &IntCurvesFace_ShapeIntersector::Load, "Sh"_a, "Tol"_a,
|
||||||
|
OCP_NOGIL)
|
||||||
|
.def("PerformNearest",
|
||||||
|
nb::overload_cast<const gp_Lin &, const Standard_Real,
|
||||||
|
const Standard_Real>(
|
||||||
|
&IntCurvesFace_ShapeIntersector::PerformNearest),
|
||||||
|
"L"_a, "PInf"_a, "PSup"_a, OCP_NOGIL)
|
||||||
|
.def("Perform",
|
||||||
|
nb::overload_cast<const gp_Lin &, const Standard_Real,
|
||||||
|
const Standard_Real>(
|
||||||
|
&IntCurvesFace_ShapeIntersector::Perform),
|
||||||
|
"L"_a, "PInf"_a, "PSup"_a, OCP_NOGIL)
|
||||||
|
.def("IsDone", &IntCurvesFace_ShapeIntersector::IsDone)
|
||||||
|
.def("NbPnt", &IntCurvesFace_ShapeIntersector::NbPnt)
|
||||||
|
.def("Pnt", &IntCurvesFace_ShapeIntersector::Pnt, "I"_a, OCP_RETURN_COPY)
|
||||||
|
.def("WParameter", &IntCurvesFace_ShapeIntersector::WParameter, "I"_a)
|
||||||
|
.def("Face", &IntCurvesFace_ShapeIntersector::Face, "I"_a,
|
||||||
|
OCP_RETURN_COPY);
|
||||||
|
}
|
||||||
61
src/modules/mod_ShapeAnalysis.cpp
Normal file
61
src/modules/mod_ShapeAnalysis.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
OCP.ShapeAnalysis — free-boundary detection.
|
||||||
|
|
||||||
|
One use: import health counts the edges bounding a single face after a
|
||||||
|
virtual sew, which is how it tells a watertight solid from a holed shell.
|
||||||
|
|
||||||
|
ConnectEdgesToWires is the one place upstream's holder semantics and this
|
||||||
|
binding's caster differ observably. Its `wires` parameter is a
|
||||||
|
Handle& that the function *reassigns*, and the caster hands C++ a copy of
|
||||||
|
the handle rather than a reference into the Python instance — so the
|
||||||
|
reassignment would be invisible. The lambda splices the result back into
|
||||||
|
the sequence the caller passed, preserving the pass-empty-then-read shape
|
||||||
|
the app uses.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
|
||||||
|
#include <ShapeAnalysis_FreeBounds.hxx>
|
||||||
|
#include <TopTools_HSequenceOfShape.hxx>
|
||||||
|
#include <TopoDS_Compound.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
|
||||||
|
void register_ShapeAnalysis(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "ShapeAnalysis");
|
||||||
|
|
||||||
|
nb::class_<ShapeAnalysis_FreeBounds> cls(m, "ShapeAnalysis_FreeBounds");
|
||||||
|
cls.def(nb::init<>())
|
||||||
|
.def(nb::init<const TopoDS_Shape &, const Standard_Real,
|
||||||
|
const Standard_Boolean, const Standard_Boolean>(),
|
||||||
|
"shape"_a, "toler"_a, "splitclosed"_a = Standard_False,
|
||||||
|
"splitopen"_a = Standard_True, OCP_NOGIL)
|
||||||
|
.def(nb::init<const TopoDS_Shape &, const Standard_Boolean,
|
||||||
|
const Standard_Boolean, const Standard_Boolean>(),
|
||||||
|
"shape"_a, "splitclosed"_a = Standard_False,
|
||||||
|
"splitopen"_a = Standard_True,
|
||||||
|
"checkinternaledges"_a = Standard_False, OCP_NOGIL)
|
||||||
|
.def("GetClosedWires", &ShapeAnalysis_FreeBounds::GetClosedWires,
|
||||||
|
OCP_RETURN_COPY)
|
||||||
|
.def("GetOpenWires", &ShapeAnalysis_FreeBounds::GetOpenWires,
|
||||||
|
OCP_RETURN_COPY);
|
||||||
|
|
||||||
|
OCP_DEF_S(
|
||||||
|
cls, "ConnectEdgesToWires",
|
||||||
|
[](opencascade::handle<TopTools_HSequenceOfShape> &edges,
|
||||||
|
const Standard_Real toler, const Standard_Boolean shared,
|
||||||
|
opencascade::handle<TopTools_HSequenceOfShape> &wires) {
|
||||||
|
opencascade::handle<TopTools_HSequenceOfShape> out;
|
||||||
|
ShapeAnalysis_FreeBounds::ConnectEdgesToWires(edges, toler, shared,
|
||||||
|
out);
|
||||||
|
// The caller keeps its own sequence object; fill that rather than
|
||||||
|
// rebinding a handle it cannot see.
|
||||||
|
if (!wires.IsNull()) {
|
||||||
|
wires->Clear();
|
||||||
|
if (!out.IsNull()) {
|
||||||
|
wires->Append(out->ChangeSequence());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edges"_a, "toler"_a, "shared"_a, "wires"_a, OCP_NOGIL);
|
||||||
|
}
|
||||||
63
src/modules/mod_ShapeFix.cpp
Normal file
63
src/modules/mod_ShapeFix.cpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
OCP.ShapeFix — geometry healing.
|
||||||
|
|
||||||
|
Used on imported geometry and after the operations that are known to
|
||||||
|
produce slightly invalid output (curved text emboss, thickened sheets).
|
||||||
|
These are transients, so their constructors go through ocp_new.
|
||||||
|
|
||||||
|
Perform() is where the work happens, not Build(), so that is what releases
|
||||||
|
the GIL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
#include "../common/occt_transient.h"
|
||||||
|
|
||||||
|
#include <ShapeFix_Face.hxx>
|
||||||
|
#include <ShapeFix_Shape.hxx>
|
||||||
|
#include <ShapeFix_Solid.hxx>
|
||||||
|
#include <TopoDS_Face.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
#include <TopoDS_Shell.hxx>
|
||||||
|
#include <TopoDS_Solid.hxx>
|
||||||
|
|
||||||
|
void register_ShapeFix(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "ShapeFix");
|
||||||
|
|
||||||
|
nb::class_<ShapeFix_Shape, Standard_Transient>(m, "ShapeFix_Shape")
|
||||||
|
.def(ocp_new<ShapeFix_Shape>())
|
||||||
|
.def(ocp_new<ShapeFix_Shape, const TopoDS_Shape &>(), "shape"_a)
|
||||||
|
.def("Init", nb::overload_cast<const TopoDS_Shape &>(&ShapeFix_Shape::Init),
|
||||||
|
"shape"_a)
|
||||||
|
.def(
|
||||||
|
"Perform", [](ShapeFix_Shape &self) { return self.Perform(); },
|
||||||
|
OCP_NOGIL)
|
||||||
|
.def("Shape", &ShapeFix_Shape::Shape, OCP_RETURN_COPY)
|
||||||
|
.def("SetPrecision", &ShapeFix_Shape::SetPrecision, "preci"_a)
|
||||||
|
.def("SetMinTolerance", &ShapeFix_Shape::SetMinTolerance, "mintol"_a)
|
||||||
|
.def("SetMaxTolerance", &ShapeFix_Shape::SetMaxTolerance, "maxtol"_a);
|
||||||
|
|
||||||
|
nb::class_<ShapeFix_Face, Standard_Transient>(m, "ShapeFix_Face")
|
||||||
|
.def(ocp_new<ShapeFix_Face>())
|
||||||
|
.def(ocp_new<ShapeFix_Face, const TopoDS_Face &>(), "face"_a)
|
||||||
|
.def(
|
||||||
|
"Perform", [](ShapeFix_Face &self) { return self.Perform(); },
|
||||||
|
OCP_NOGIL)
|
||||||
|
.def("FixOrientation",
|
||||||
|
nb::overload_cast<>(&ShapeFix_Face::FixOrientation), OCP_NOGIL)
|
||||||
|
.def("Face", &ShapeFix_Face::Face, OCP_RETURN_COPY);
|
||||||
|
|
||||||
|
nb::class_<ShapeFix_Solid, Standard_Transient>(m, "ShapeFix_Solid")
|
||||||
|
.def(ocp_new<ShapeFix_Solid>())
|
||||||
|
.def(ocp_new<ShapeFix_Solid, const TopoDS_Solid &>(), "solid"_a)
|
||||||
|
.def(
|
||||||
|
"Perform", [](ShapeFix_Solid &self) { return self.Perform(); },
|
||||||
|
OCP_NOGIL)
|
||||||
|
.def(
|
||||||
|
"SolidFromShell",
|
||||||
|
[](ShapeFix_Solid &self, const TopoDS_Shell &shell) {
|
||||||
|
return self.SolidFromShell(shell);
|
||||||
|
},
|
||||||
|
"shell"_a, OCP_RETURN_COPY, OCP_NOGIL)
|
||||||
|
.def("Shape", &ShapeFix_Solid::Shape, OCP_RETURN_COPY);
|
||||||
|
}
|
||||||
35
src/modules/mod_ShapeUpgrade.cpp
Normal file
35
src/modules/mod_ShapeUpgrade.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
OCP.ShapeUpgrade — merge faces and edges that share a surface.
|
||||||
|
|
||||||
|
The app runs this after almost every fuse, so a bidirectional extrude ends
|
||||||
|
up with the face count of a single prism rather than two prisms glued at
|
||||||
|
the sketch plane. Not a MakeShape, so Build/Shape are its own.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common/occt_module.h"
|
||||||
|
#include "../common/occt_policies.h"
|
||||||
|
#include "../common/occt_transient.h"
|
||||||
|
|
||||||
|
#include <ShapeUpgrade_UnifySameDomain.hxx>
|
||||||
|
#include <TopoDS_Shape.hxx>
|
||||||
|
|
||||||
|
void register_ShapeUpgrade(nb::module_ &root) {
|
||||||
|
nb::module_ m = ocp_submodule(root, "ShapeUpgrade");
|
||||||
|
|
||||||
|
nb::class_<ShapeUpgrade_UnifySameDomain, Standard_Transient>(
|
||||||
|
m, "ShapeUpgrade_UnifySameDomain")
|
||||||
|
.def(ocp_new<ShapeUpgrade_UnifySameDomain>())
|
||||||
|
.def(ocp_new<ShapeUpgrade_UnifySameDomain, const TopoDS_Shape &,
|
||||||
|
const Standard_Boolean, const Standard_Boolean,
|
||||||
|
const Standard_Boolean>(),
|
||||||
|
"aShape"_a, "UnifyEdges"_a = Standard_True,
|
||||||
|
"UnifyFaces"_a = Standard_True,
|
||||||
|
"ConcatBSplines"_a = Standard_False)
|
||||||
|
.def("Initialize", &ShapeUpgrade_UnifySameDomain::Initialize, "aShape"_a,
|
||||||
|
"UnifyEdges"_a = Standard_True, "UnifyFaces"_a = Standard_True,
|
||||||
|
"ConcatBSplines"_a = Standard_False)
|
||||||
|
.def("SetSafeInputMode", &ShapeUpgrade_UnifySameDomain::SetSafeInputMode,
|
||||||
|
"theValue"_a)
|
||||||
|
.def("Build", &ShapeUpgrade_UnifySameDomain::Build, OCP_NOGIL)
|
||||||
|
.def("Shape", &ShapeUpgrade_UnifySameDomain::Shape, OCP_RETURN_COPY);
|
||||||
|
}
|
||||||
@@ -10,11 +10,13 @@
|
|||||||
|
|
||||||
#include "../common/occt_module.h"
|
#include "../common/occt_module.h"
|
||||||
#include "../common/occt_policies.h"
|
#include "../common/occt_policies.h"
|
||||||
|
#include "../common/occt_transient.h"
|
||||||
|
|
||||||
#include <nanobind/make_iterator.h>
|
#include <nanobind/make_iterator.h>
|
||||||
|
|
||||||
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
||||||
#include <TopTools_IndexedMapOfShape.hxx>
|
#include <TopTools_IndexedMapOfShape.hxx>
|
||||||
|
#include <TopTools_HSequenceOfShape.hxx>
|
||||||
#include <TopTools_ListOfShape.hxx>
|
#include <TopTools_ListOfShape.hxx>
|
||||||
|
|
||||||
void register_TopTools(nb::module_ &root) {
|
void register_TopTools(nb::module_ &root) {
|
||||||
@@ -97,4 +99,26 @@ void register_TopTools(nb::module_ &root) {
|
|||||||
self.Clear(Standard_True);
|
self.Clear(Standard_True);
|
||||||
})
|
})
|
||||||
.def("__len__", &TopTools_IndexedDataMapOfShapeListOfShape::Size);
|
.def("__len__", &TopTools_IndexedDataMapOfShapeListOfShape::Size);
|
||||||
|
|
||||||
|
// A transient sequence, unlike the maps above — ShapeAnalysis_FreeBounds
|
||||||
|
// takes and returns it by handle.
|
||||||
|
nb::class_<TopTools_HSequenceOfShape, Standard_Transient>(
|
||||||
|
m, "TopTools_HSequenceOfShape")
|
||||||
|
.def(ocp_new<TopTools_HSequenceOfShape>())
|
||||||
|
.def(
|
||||||
|
"Append",
|
||||||
|
[](TopTools_HSequenceOfShape &self, const TopoDS_Shape &item) {
|
||||||
|
self.Append(item);
|
||||||
|
},
|
||||||
|
"theItem"_a)
|
||||||
|
.def("Length", &TopTools_HSequenceOfShape::Length)
|
||||||
|
.def("IsEmpty", &TopTools_HSequenceOfShape::IsEmpty)
|
||||||
|
.def(
|
||||||
|
"Value",
|
||||||
|
[](const TopTools_HSequenceOfShape &self, Standard_Integer index) {
|
||||||
|
return self.Value(index);
|
||||||
|
},
|
||||||
|
"theIndex"_a, OCP_RETURN_COPY)
|
||||||
|
.def("Clear", [](TopTools_HSequenceOfShape &self) { self.Clear(); })
|
||||||
|
.def("__len__", &TopTools_HSequenceOfShape::Length);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -499,5 +499,197 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"inc2": {
|
||||||
|
"fillet": {
|
||||||
|
"sha256": "4da9eb256a1e012b94655f6ecd02b34590c4e058beb336794fb68c3047c0070c",
|
||||||
|
"volume": 5974.247779607694,
|
||||||
|
"area": 2172.530964914873,
|
||||||
|
"bbox": [
|
||||||
|
-1.000000004440892e-07,
|
||||||
|
-1.000000004440892e-07,
|
||||||
|
-1e-07,
|
||||||
|
10.0000001,
|
||||||
|
20.0000001,
|
||||||
|
30.0000001
|
||||||
|
],
|
||||||
|
"n_faces": 7,
|
||||||
|
"n_edges": 15,
|
||||||
|
"n_solids": 1,
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"pre": 0,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"generated": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 1,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [],
|
||||||
|
"generated": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 2,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
37
|
||||||
|
],
|
||||||
|
"generated": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 3,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [],
|
||||||
|
"generated": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 4,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
32
|
||||||
|
],
|
||||||
|
"generated": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 5,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
13
|
||||||
|
],
|
||||||
|
"generated": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"chamfer": {
|
||||||
|
"sha256": "cdf8d040a239e6202f94e38fc0fc9595e475180c7067a5f1e2391d9ed95d03af",
|
||||||
|
"volume": 5966.25,
|
||||||
|
"area": 2171.389610306789,
|
||||||
|
"bbox": [
|
||||||
|
-1.000000001110223e-07,
|
||||||
|
-1.000000001110223e-07,
|
||||||
|
-1e-07,
|
||||||
|
10.0000001,
|
||||||
|
20.0000001,
|
||||||
|
30.000000100000005
|
||||||
|
],
|
||||||
|
"n_faces": 7,
|
||||||
|
"n_edges": 15,
|
||||||
|
"n_solids": 1
|
||||||
|
},
|
||||||
|
"splitter": {
|
||||||
|
"volume": 6000.0,
|
||||||
|
"area": 3400.0,
|
||||||
|
"bbox": [
|
||||||
|
-1.5e-07,
|
||||||
|
-1.5e-07,
|
||||||
|
-1.5e-07,
|
||||||
|
10.00000015,
|
||||||
|
20.00000015,
|
||||||
|
30.00000015
|
||||||
|
],
|
||||||
|
"n_faces": 16,
|
||||||
|
"n_edges": 28,
|
||||||
|
"n_solids": 3,
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"pre": 0,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
3,
|
||||||
|
37,
|
||||||
|
61
|
||||||
|
],
|
||||||
|
"generated": [
|
||||||
|
8,
|
||||||
|
41
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 1,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
33,
|
||||||
|
57,
|
||||||
|
81
|
||||||
|
],
|
||||||
|
"generated": [
|
||||||
|
24,
|
||||||
|
53
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 2,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [],
|
||||||
|
"generated": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 3,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [],
|
||||||
|
"generated": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 4,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
25,
|
||||||
|
54,
|
||||||
|
78
|
||||||
|
],
|
||||||
|
"generated": [
|
||||||
|
22,
|
||||||
|
51
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pre": 5,
|
||||||
|
"deleted": false,
|
||||||
|
"modified": [
|
||||||
|
13,
|
||||||
|
44,
|
||||||
|
68
|
||||||
|
],
|
||||||
|
"generated": [
|
||||||
|
15,
|
||||||
|
46
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unified": {
|
||||||
|
"volume": 12500.0,
|
||||||
|
"area": 3650.0,
|
||||||
|
"bbox": [
|
||||||
|
-1.5e-07,
|
||||||
|
-1.5e-07,
|
||||||
|
-1.5e-07,
|
||||||
|
25.00000015,
|
||||||
|
25.00000015,
|
||||||
|
30.00000015
|
||||||
|
],
|
||||||
|
"n_faces": 12,
|
||||||
|
"n_edges": 30,
|
||||||
|
"n_solids": 1
|
||||||
|
},
|
||||||
|
"distance": {
|
||||||
|
"is_done": true,
|
||||||
|
"value": 30.0,
|
||||||
|
"n_solutions": 4
|
||||||
|
},
|
||||||
|
"classifier": {
|
||||||
|
"inside": 0,
|
||||||
|
"outside": 1
|
||||||
|
},
|
||||||
|
"uv_bounds": [
|
||||||
|
0.0,
|
||||||
|
30.0,
|
||||||
|
-20.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"outer_wire_edges": 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
224
tests/test_inc2_features.py
Normal file
224
tests/test_inc2_features.py
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
"""Inc 2 gate: the feature tail reproduces the stock wheel's answers.
|
||||||
|
|
||||||
|
Same contract as test_inc1_modeling.py — see its module docstring for why the
|
||||||
|
app's own suite cannot gate an increment and what the tolerances mean.
|
||||||
|
|
||||||
|
The fillet history is the load-bearing check here: a blend's provenance is read
|
||||||
|
exactly like a boolean's, and it is what attributes the new faces back to the
|
||||||
|
feature that made them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from OCP.BRepAlgoAPI import BRepAlgoAPI_Splitter
|
||||||
|
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
||||||
|
from OCP.BRepExtrema import BRepExtrema_DistShapeShape
|
||||||
|
from OCP.BRepFilletAPI import BRepFilletAPI_MakeChamfer, BRepFilletAPI_MakeFillet
|
||||||
|
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
|
||||||
|
from OCP.BRepTools import BRepTools, BRepTools_WireExplorer
|
||||||
|
from OCP.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
|
||||||
|
from OCP.TopAbs import TopAbs_EDGE, TopAbs_FACE
|
||||||
|
from OCP.TopoDS import TopoDS
|
||||||
|
from OCP.TopTools import TopTools_ListOfShape
|
||||||
|
|
||||||
|
from .test_inc1_modeling import assert_close, brep_sha, history, measure, sub_shapes
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def inc2(manifest):
|
||||||
|
if "inc2" not in manifest:
|
||||||
|
pytest.skip("manifest predates the Inc 2 reference block")
|
||||||
|
return manifest["inc2"]
|
||||||
|
|
||||||
|
|
||||||
|
def _box():
|
||||||
|
return BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()
|
||||||
|
|
||||||
|
|
||||||
|
def _compare(got: dict, expected: dict, what: str) -> None:
|
||||||
|
for key in ("n_faces", "n_edges", "n_solids"):
|
||||||
|
assert got[key] == expected[key], f"{what}.{key}"
|
||||||
|
for key in ("volume", "area", "bbox"):
|
||||||
|
assert_close(got[key], expected[key], f"{what}.{key}")
|
||||||
|
|
||||||
|
|
||||||
|
def test_fillet_matches_stock_including_history(inc2):
|
||||||
|
box = _box()
|
||||||
|
maker = BRepFilletAPI_MakeFillet(box)
|
||||||
|
maker.Add(2.0, TopoDS.Edge_s(sub_shapes(box, TopAbs_EDGE)[0]))
|
||||||
|
maker.Build()
|
||||||
|
assert maker.IsDone()
|
||||||
|
|
||||||
|
filleted = maker.Shape()
|
||||||
|
expected = inc2["fillet"]
|
||||||
|
assert brep_sha(filleted) == expected["sha256"]
|
||||||
|
_compare(measure(filleted), expected, "fillet")
|
||||||
|
# The blend's provenance, read the same way a boolean's is.
|
||||||
|
assert history(maker, box, filleted) == expected["history"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_chamfer_matches_stock(inc2):
|
||||||
|
box = _box()
|
||||||
|
maker = BRepFilletAPI_MakeChamfer(box)
|
||||||
|
maker.Add(1.5, TopoDS.Edge_s(sub_shapes(box, TopAbs_EDGE)[0]))
|
||||||
|
maker.Build()
|
||||||
|
assert maker.IsDone()
|
||||||
|
|
||||||
|
expected = inc2["chamfer"]
|
||||||
|
assert brep_sha(maker.Shape()) == expected["sha256"]
|
||||||
|
_compare(measure(maker.Shape()), expected, "chamfer")
|
||||||
|
|
||||||
|
|
||||||
|
def test_splitter_matches_stock_including_history(inc2):
|
||||||
|
from OCP.gp import gp_Pnt
|
||||||
|
|
||||||
|
box = _box()
|
||||||
|
splitter = BRepAlgoAPI_Splitter()
|
||||||
|
args, tools = TopTools_ListOfShape(), TopTools_ListOfShape()
|
||||||
|
args.Append(box)
|
||||||
|
tools.Append(BRepPrimAPI_MakeBox(gp_Pnt(-5, 5, -5), 20.0, 1.0, 40.0).Shape())
|
||||||
|
splitter.SetArguments(args)
|
||||||
|
splitter.SetTools(tools)
|
||||||
|
splitter.Build()
|
||||||
|
assert splitter.IsDone()
|
||||||
|
|
||||||
|
expected = inc2["splitter"]
|
||||||
|
_compare(measure(splitter.Shape()), expected, "splitter")
|
||||||
|
# Region attribution in the sketch builder reads exactly this.
|
||||||
|
assert history(splitter, box, splitter.Shape()) == expected["history"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_unify_same_domain_matches_stock(inc2, fixture_shapes):
|
||||||
|
unify = ShapeUpgrade_UnifySameDomain(fixture_shapes["fused"], True, True, True)
|
||||||
|
unify.Build()
|
||||||
|
_compare(measure(unify.Shape()), inc2["unified"], "unified")
|
||||||
|
|
||||||
|
|
||||||
|
def test_distance_and_classifier_match_stock(inc2):
|
||||||
|
from OCP.gp import gp_Pnt
|
||||||
|
|
||||||
|
dist = BRepExtrema_DistShapeShape(
|
||||||
|
_box(), BRepPrimAPI_MakeBox(gp_Pnt(40, 0, 0), 5.0, 5.0, 5.0).Shape()
|
||||||
|
)
|
||||||
|
assert dist.IsDone() == inc2["distance"]["is_done"]
|
||||||
|
assert dist.NbSolution() == inc2["distance"]["n_solutions"]
|
||||||
|
assert_close(dist.Value(), inc2["distance"]["value"], "distance")
|
||||||
|
|
||||||
|
box = _box()
|
||||||
|
inside = BRepClass3d_SolidClassifier(box, gp_Pnt(5, 10, 15), 1e-7)
|
||||||
|
outside = BRepClass3d_SolidClassifier(box, gp_Pnt(50, 10, 15), 1e-7)
|
||||||
|
assert int(inside.State()) == inc2["classifier"]["inside"]
|
||||||
|
assert int(outside.State()) == inc2["classifier"]["outside"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_brep_tools_queries_match_stock(inc2):
|
||||||
|
face = TopoDS.Face_s(sub_shapes(_box(), TopAbs_FACE)[0])
|
||||||
|
assert_close(list(BRepTools.UVBounds_s(face)), inc2["uv_bounds"], "uv_bounds")
|
||||||
|
outer = BRepTools.OuterWire_s(face)
|
||||||
|
assert len(sub_shapes(outer, TopAbs_EDGE)) == inc2["outer_wire_edges"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wire_explorer_walks_in_connection_order():
|
||||||
|
"""WireExplorer follows connectivity, which is the whole reason to use it
|
||||||
|
over the map order MapShapes gives."""
|
||||||
|
face = TopoDS.Face_s(sub_shapes(_box(), TopAbs_FACE)[0])
|
||||||
|
explorer = BRepTools_WireExplorer(BRepTools.OuterWire_s(face))
|
||||||
|
walked = []
|
||||||
|
while explorer.More():
|
||||||
|
walked.append(explorer.Current())
|
||||||
|
explorer.Next()
|
||||||
|
assert len(walked) == 4
|
||||||
|
# Results outlive the explorer: sub-shapes come back by value.
|
||||||
|
del explorer
|
||||||
|
assert all(not e.IsNull() for e in walked)
|
||||||
|
|
||||||
|
|
||||||
|
def test_clean_drops_the_existing_triangulation():
|
||||||
|
"""OCCT keeps whatever mesh a shape already carries, so re-meshing at a
|
||||||
|
different deflection is a no-op without this — which is why the
|
||||||
|
tessellation path calls it before its second pass."""
|
||||||
|
from OCP.BRep import BRep_Tool
|
||||||
|
from OCP.BRepMesh import BRepMesh_IncrementalMesh
|
||||||
|
from OCP.TopLoc import TopLoc_Location
|
||||||
|
|
||||||
|
shape = _box()
|
||||||
|
BRepMesh_IncrementalMesh(shape, 0.1, False, 0.5, True)
|
||||||
|
face = TopoDS.Face_s(sub_shapes(shape, TopAbs_FACE)[0])
|
||||||
|
assert BRep_Tool.Triangulation_s(face, TopLoc_Location()) is not None
|
||||||
|
|
||||||
|
BRepTools.Clean_s(shape)
|
||||||
|
face = TopoDS.Face_s(sub_shapes(shape, TopAbs_FACE)[0])
|
||||||
|
assert BRep_Tool.Triangulation_s(face, TopLoc_Location()) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_free_bounds_reports_an_open_shell():
|
||||||
|
"""A watertight solid has no free boundary; a shell missing one face
|
||||||
|
exposes that face's four edges. This is how import health tells the two
|
||||||
|
apart, and it exercises Sewing on the way.
|
||||||
|
|
||||||
|
Note it wants a genuinely sewn shell: a bare face reports nothing, in this
|
||||||
|
binding and in the stock wheel alike.
|
||||||
|
"""
|
||||||
|
from OCP.BRepBuilderAPI import BRepBuilderAPI_Sewing
|
||||||
|
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds
|
||||||
|
|
||||||
|
def free_edges(shape) -> int:
|
||||||
|
bounds = ShapeAnalysis_FreeBounds(shape, 1e-6)
|
||||||
|
return sum(
|
||||||
|
len(sub_shapes(wires, TopAbs_EDGE))
|
||||||
|
for wires in (bounds.GetClosedWires(), bounds.GetOpenWires())
|
||||||
|
if not wires.IsNull()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert free_edges(_box()) == 0
|
||||||
|
|
||||||
|
box = _box()
|
||||||
|
sewing = BRepBuilderAPI_Sewing(1e-6)
|
||||||
|
for face in sub_shapes(box, TopAbs_FACE)[:-1]: # leave one face off
|
||||||
|
sewing.Add(face)
|
||||||
|
sewing.Perform()
|
||||||
|
assert free_edges(sewing.SewedShape()) == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_ray_intersector_finds_the_opposite_wall():
|
||||||
|
"""The surface-pattern feature measures wall thickness this way."""
|
||||||
|
from OCP.gp import gp_Dir, gp_Lin, gp_Pnt
|
||||||
|
from OCP.IntCurvesFace import IntCurvesFace_ShapeIntersector
|
||||||
|
|
||||||
|
inter = IntCurvesFace_ShapeIntersector()
|
||||||
|
inter.Load(_box(), 1e-7)
|
||||||
|
inter.PerformNearest(
|
||||||
|
gp_Lin(gp_Pnt(-5.0, 10.0, 15.0), gp_Dir(1.0, 0.0, 0.0)), 0.0, 1.0e6
|
||||||
|
)
|
||||||
|
assert inter.IsDone()
|
||||||
|
assert inter.NbPnt() > 0
|
||||||
|
assert inter.WParameter(1) == pytest.approx(5.0, abs=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def test_curve_length_matches_the_edge():
|
||||||
|
from OCP.BRepAdaptor import BRepAdaptor_Curve
|
||||||
|
from OCP.GCPnts import GCPnts_AbscissaPoint, GCPnts_TangentialDeflection
|
||||||
|
|
||||||
|
edge = TopoDS.Edge_s(sub_shapes(_box(), TopAbs_EDGE)[0])
|
||||||
|
adaptor = BRepAdaptor_Curve(edge)
|
||||||
|
length = GCPnts_AbscissaPoint.Length_s(adaptor)
|
||||||
|
assert length in (pytest.approx(10.0), pytest.approx(20.0), pytest.approx(30.0))
|
||||||
|
|
||||||
|
# The discretiser the viewport's edge polylines come from.
|
||||||
|
points = GCPnts_TangentialDeflection(adaptor, 0.15, 0.05)
|
||||||
|
assert points.NbPoints() >= 2
|
||||||
|
assert not points.Value(1).IsEqual(points.Value(points.NbPoints()), 1e-9)
|
||||||
|
|
||||||
|
|
||||||
|
def test_thick_solid_by_simple_thickens_a_face():
|
||||||
|
"""The shape rib and the open-profile extrude both lean on this."""
|
||||||
|
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeThickSolid
|
||||||
|
|
||||||
|
face = TopoDS.Face_s(sub_shapes(_box(), TopAbs_FACE)[0])
|
||||||
|
maker = BRepOffsetAPI_MakeThickSolid()
|
||||||
|
maker.MakeThickSolidBySimple(face, 2.0)
|
||||||
|
maker.Build()
|
||||||
|
assert maker.IsDone()
|
||||||
|
assert measure(maker.Shape())["n_solids"] == 1
|
||||||
@@ -314,6 +314,92 @@ def _constructions() -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _inc2_reference() -> dict:
|
||||||
|
"""Fillet, chamfer, splitter, healing and the query classes.
|
||||||
|
|
||||||
|
The fillet history is the point of this block: a blend's provenance is read
|
||||||
|
exactly like a boolean's, and it is what attributes the new faces back to
|
||||||
|
the feature that made them.
|
||||||
|
"""
|
||||||
|
from OCP.BRepAlgoAPI import BRepAlgoAPI_Splitter
|
||||||
|
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
|
||||||
|
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
||||||
|
from OCP.BRepExtrema import BRepExtrema_DistShapeShape
|
||||||
|
from OCP.BRepFilletAPI import BRepFilletAPI_MakeChamfer, BRepFilletAPI_MakeFillet
|
||||||
|
from OCP.BRepTools import BRepTools
|
||||||
|
from OCP.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
|
||||||
|
from OCP.TopAbs import TopAbs_EDGE
|
||||||
|
from OCP.TopoDS import TopoDS
|
||||||
|
from OCP.TopTools import TopTools_ListOfShape
|
||||||
|
|
||||||
|
box = _box()
|
||||||
|
edges = _sub_shapes(box, TopAbs_EDGE)
|
||||||
|
|
||||||
|
fillet = BRepFilletAPI_MakeFillet(box)
|
||||||
|
fillet.Add(2.0, TopoDS.Edge_s(edges[0]))
|
||||||
|
fillet.Build()
|
||||||
|
filleted = fillet.Shape()
|
||||||
|
|
||||||
|
# The edge must belong to the very shape the builder was given, not an
|
||||||
|
# equal one built separately — OCCT matches by identity.
|
||||||
|
chamfer_box = _box()
|
||||||
|
chamfer = BRepFilletAPI_MakeChamfer(chamfer_box)
|
||||||
|
chamfer.Add(1.5, TopoDS.Edge_s(_sub_shapes(chamfer_box, TopAbs_EDGE)[0]))
|
||||||
|
chamfer.Build()
|
||||||
|
chamfered = chamfer.Shape()
|
||||||
|
|
||||||
|
# A splitter over an edge, mirroring how sketch regions are cut apart.
|
||||||
|
splitter = BRepAlgoAPI_Splitter()
|
||||||
|
split_box = _box()
|
||||||
|
args, tools = TopTools_ListOfShape(), TopTools_ListOfShape()
|
||||||
|
args.Append(split_box)
|
||||||
|
tools.Append(
|
||||||
|
BRepPrimAPI_MakeBox(gp_Pnt(-5, 5, -5), 20.0, 1.0, 40.0).Shape()
|
||||||
|
)
|
||||||
|
splitter.SetArguments(args)
|
||||||
|
splitter.SetTools(tools)
|
||||||
|
splitter.Build()
|
||||||
|
|
||||||
|
unify = ShapeUpgrade_UnifySameDomain(_fused(), True, True, True)
|
||||||
|
unify.Build()
|
||||||
|
|
||||||
|
dist = BRepExtrema_DistShapeShape(
|
||||||
|
_box(), BRepPrimAPI_MakeBox(gp_Pnt(40, 0, 0), 5.0, 5.0, 5.0).Shape()
|
||||||
|
)
|
||||||
|
|
||||||
|
inside = BRepClass3d_SolidClassifier(box, gp_Pnt(5, 10, 15), 1e-7)
|
||||||
|
outside = BRepClass3d_SolidClassifier(box, gp_Pnt(50, 10, 15), 1e-7)
|
||||||
|
|
||||||
|
face0 = TopoDS.Face_s(_sub_shapes(box, TopAbs_FACE)[0])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"fillet": {
|
||||||
|
"sha256": hashlib.sha256(write_bytes(filleted)).hexdigest(),
|
||||||
|
**_measure(filleted),
|
||||||
|
"history": _history(fillet, box, filleted),
|
||||||
|
},
|
||||||
|
"chamfer": {
|
||||||
|
"sha256": hashlib.sha256(write_bytes(chamfered)).hexdigest(),
|
||||||
|
**_measure(chamfered),
|
||||||
|
},
|
||||||
|
"splitter": {
|
||||||
|
**_measure(splitter.Shape()),
|
||||||
|
"history": _history(splitter, split_box, splitter.Shape()),
|
||||||
|
},
|
||||||
|
"unified": _measure(unify.Shape()),
|
||||||
|
"distance": {
|
||||||
|
"is_done": bool(dist.IsDone()),
|
||||||
|
"value": dist.Value(),
|
||||||
|
"n_solutions": dist.NbSolution(),
|
||||||
|
},
|
||||||
|
"classifier": {"inside": int(inside.State()), "outside": int(outside.State())},
|
||||||
|
"uv_bounds": list(BRepTools.UVBounds_s(face0)),
|
||||||
|
"outer_wire_edges": len(
|
||||||
|
_sub_shapes(BRepTools.OuterWire_s(face0), TopAbs_EDGE)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _inc1_reference() -> dict:
|
def _inc1_reference() -> dict:
|
||||||
op, base, _tool = _cut_cylinder_op()
|
op, base, _tool = _cut_cylinder_op()
|
||||||
return {
|
return {
|
||||||
@@ -359,6 +445,7 @@ def main() -> int:
|
|||||||
print(f"{name}: {len(data)} bytes, {manifest['shapes'][name]['faces']} faces")
|
print(f"{name}: {len(data)} bytes, {manifest['shapes'][name]['faces']} faces")
|
||||||
|
|
||||||
manifest["inc1"] = _inc1_reference()
|
manifest["inc1"] = _inc1_reference()
|
||||||
|
manifest["inc2"] = _inc2_reference()
|
||||||
|
|
||||||
(OUT / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
|
(OUT / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
|
||||||
print(f"\nwrote {len(SHAPES)} fixtures + manifest to {OUT}")
|
print(f"\nwrote {len(SHAPES)} fixtures + manifest to {OUT}")
|
||||||
|
|||||||
Reference in New Issue
Block a user