/* OCP.TopoDS — shapes are value types, and that is the point. A TopoDS_Shape is a small value (a handle to its TShape, a location, an orientation), so every shape crossing into Python is an owned copy. A wrapper can therefore never alias storage owned by an explorer, a map or a BOP history list — which is the lifetime class that segfaulted a process-global face memo under upstream OCP. Fidelity note on __hash__ / __eq__, verified against the stock wheel: upstream binds __hash__ (TShape + Location) and leaves __eq__ at Python's default identity comparison. That pairing looks odd — two re-extracted copies of one face hash equal but compare unequal — and it is exactly what cad/topology/geom_memo.py is built around: it buckets on hash(face) and disambiguates with IsSame, because == cannot be trusted. Binding __eq__ to IsEqual here would silently change that memo's behaviour, so we match upstream rather than improve on it. */ #include "../common/occt_module.h" #include "../common/occt_policies.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { /// Stands in for the TopoDS namespace so its statics can hang off a Python /// class of that name — see the comment at the downcast block below. struct TopoDSStatics {}; } // namespace void register_TopoDS(nb::module_ &root) { nb::module_ m = ocp_submodule(root, "TopoDS"); nb::class_(m, "TopoDS_Shape") .def(nb::init<>()) .def("IsNull", &TopoDS_Shape::IsNull) .def("Nullify", &TopoDS_Shape::Nullify) .def("ShapeType", &TopoDS_Shape::ShapeType) .def("Orientation", nb::overload_cast<>(&TopoDS_Shape::Orientation, nb::const_)) .def("Location", nb::overload_cast<>(&TopoDS_Shape::Location, nb::const_), OCP_RETURN_COPY) .def("Closed", nb::overload_cast<>(&TopoDS_Shape::Closed, nb::const_)) .def("Reverse", &TopoDS_Shape::Reverse) .def("Reversed", &TopoDS_Shape::Reversed, OCP_RETURN_COPY) .def("Moved", &TopoDS_Shape::Moved, "position"_a, "raiseExc"_a = Standard_False, OCP_RETURN_COPY) .def("Located", &TopoDS_Shape::Located, "loc"_a, "raiseExc"_a = Standard_False, OCP_RETURN_COPY) .def("IsSame", &TopoDS_Shape::IsSame, "other"_a) .def("IsEqual", &TopoDS_Shape::IsEqual, "other"_a) .def("IsPartner", &TopoDS_Shape::IsPartner, "other"_a) .def("__hash__", [](const TopoDS_Shape &s) { return std::hash{}(s); }); #define OCP_SHAPE_SUBCLASS(Type) \ nb::class_(m, #Type).def(nb::init<>()) OCP_SHAPE_SUBCLASS(TopoDS_Vertex); OCP_SHAPE_SUBCLASS(TopoDS_Edge); OCP_SHAPE_SUBCLASS(TopoDS_Wire); OCP_SHAPE_SUBCLASS(TopoDS_Face); OCP_SHAPE_SUBCLASS(TopoDS_Shell); OCP_SHAPE_SUBCLASS(TopoDS_Solid); OCP_SHAPE_SUBCLASS(TopoDS_CompSolid); OCP_SHAPE_SUBCLASS(TopoDS_Compound); #undef OCP_SHAPE_SUBCLASS nb::class_(m, "TopoDS_Iterator") .def(nb::init<>()) .def(nb::init(), "S"_a, "cumOri"_a = Standard_True, "cumLoc"_a = Standard_True) .def("More", &TopoDS_Iterator::More) .def("Next", &TopoDS_Iterator::Next) .def("Value", &TopoDS_Iterator::Value, OCP_RETURN_COPY); // Checked downcasts. These raise Standard_TypeMismatch on a kind // mismatch, which the translator turns into a RuntimeError subclass. // // OCCT 7.9 turned TopoDS from a class into a namespace, but upstream OCP // still presents it as a class carrying the _s statics, and the app calls // TopoDS.Face_s(...). An empty carrier type reproduces that surface. nb::class_ cls(m, "TopoDS"); #define OCP_DOWNCAST(Name) \ OCP_DEF_S( \ cls, #Name, \ [](const TopoDS_Shape &s) { return TopoDS::Name(s); }, "S"_a, \ OCP_RETURN_COPY) OCP_DOWNCAST(Vertex); OCP_DOWNCAST(Edge); OCP_DOWNCAST(Wire); OCP_DOWNCAST(Face); OCP_DOWNCAST(Shell); OCP_DOWNCAST(Solid); OCP_DOWNCAST(CompSolid); OCP_DOWNCAST(Compound); #undef OCP_DOWNCAST }