Phase 10A/10B Inc 0: build system, handle model, first module surface
Builds n3xd-ocp end to end and publishes 7.9.3.1.dev1 to the Gitea registry, where it installs anonymously and passes its suite. - occt/Dockerfile: OCCT 7.9.3 compiled once into a manylinux_2_28 builder image (base digest + tarball sha256 pinned), Draw/VTK/Tk/Xlib/OpenGL off, FreeType on, -O2 without fast-math or march=native. A final layer asserts TKService/TKV3d exist with no libGL/libX11 DT_NEEDED, which is what lets the app image drop libgl1/libx11-6. Mounted into, never built FROM. - scikit-build-core + nanobind STABLE_ABI -> one cp312-abi3 extension that registers every OCP.* submodule via PyImport_AddModule, so `import OCP.TopoDS` needs no shim and cls.__module__ is right. Version <occt>.N is asserted against the OCCT found, keeping occt_version() truthful. - occt_handle.h: type caster for opencascade::handle<T> over OCCT's intrusive refcount. Wrappers are non-owning instances holding exactly one handle in their keep-alive list, reusing an existing wrapper so identity survives a round trip. Transient constructors go through ocp_new (never nb::init<>, which would let OCCT delete nanobind's storage); the caster refuses a refcount-0 object rather than corrupt the heap. Verified under ASAN with no memory-safety errors, plus an RSS bound over 50k create/destroy cycles. - Sub-shapes are returned by value everywhere, making the TShape lifetime class that segfaulted a process-global face memo unrepresentable. - Standard_Failure derives RuntimeError, with ~20 concrete types dispatched on the dynamic OCCT type (cad_pool marshals failures home by type name). - Inc 0 surface: gp subset, TopAbs, TopoDS (+ downcasts), TopExp, TopLoc, TopTools, BRep, BinTools, Poly, Standard. 34 of the app's 139 symbols. - n3xd_ocp: additive APIs kept out of the OCP namespace so parity testing stays meaningful. bintools (shape <-> bytes, GIL-free, byte-identical) and _debug. Two findings worth the record, both verified against the stock wheel rather than assumed: upstream binds __hash__ but leaves __eq__ at identity, which is exactly what geom_memo.py's hash-bucket + IsSame scan is built around, so we match it instead of "fixing" it; and BinTools can release the GIL after all, by slurping the file-like object instead of bridging a streambuf that would call back into Python. Gate: BREP round-trips are byte-identical to cadquery-ocp-novtk across six fixtures (the generator asserts stock idempotency first). That matters beyond IPC — derive.py content-addresses BREP payloads by sha256 and stores the ref.
This commit is contained in:
109
src/common/occt_exceptions.cpp
Normal file
109
src/common/occt_exceptions.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "occt_exceptions.h"
|
||||
#include "occt_module.h"
|
||||
|
||||
#include <Standard_ConstructionError.hxx>
|
||||
#include <Standard_DimensionError.hxx>
|
||||
#include <Standard_DimensionMismatch.hxx>
|
||||
#include <Standard_DivideByZero.hxx>
|
||||
#include <Standard_DomainError.hxx>
|
||||
#include <Standard_Failure.hxx>
|
||||
#include <Standard_ImmutableObject.hxx>
|
||||
#include <Standard_NoSuchObject.hxx>
|
||||
#include <Standard_NotImplemented.hxx>
|
||||
#include <Standard_NullObject.hxx>
|
||||
#include <Standard_NullValue.hxx>
|
||||
#include <Standard_NumericError.hxx>
|
||||
#include <Standard_OutOfRange.hxx>
|
||||
#include <Standard_Overflow.hxx>
|
||||
#include <Standard_ProgramError.hxx>
|
||||
#include <Standard_RangeError.hxx>
|
||||
#include <Standard_TypeMismatch.hxx>
|
||||
#include <Standard_Underflow.hxx>
|
||||
#include <StdFail_NotDone.hxx>
|
||||
#include <StdFail_UndefinedDerivative.hxx>
|
||||
#include <StdFail_UndefinedValue.hxx>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
/// OCCT class name -> Python exception type. Populated at import; read-only
|
||||
/// afterwards, so it is fork-safe and needs no lock.
|
||||
std::unordered_map<std::string, PyObject *> g_exc_types;
|
||||
PyObject *g_base = nullptr; // OCP.Standard.Standard_Failure
|
||||
|
||||
PyObject *make_exc(nb::module_ &mod, const char *qualified, const char *name,
|
||||
PyObject *base) {
|
||||
PyObject *exc = PyErr_NewException(qualified, base, nullptr);
|
||||
if (!exc)
|
||||
throw nb::python_error();
|
||||
mod.attr(name) = nb::borrow(exc);
|
||||
g_exc_types.emplace(name, exc);
|
||||
return exc;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_occt_exceptions(nb::module_ &root) {
|
||||
nb::module_ std_mod = ocp_submodule(root, "Standard");
|
||||
nb::module_ fail_mod = ocp_submodule(root, "StdFail");
|
||||
|
||||
// Standard_Failure derives RuntimeError: that single fact is what keeps
|
||||
// the backend's `except RuntimeError` sites working unchanged.
|
||||
g_base = make_exc(std_mod, "OCP.Standard.Standard_Failure",
|
||||
"Standard_Failure", PyExc_RuntimeError);
|
||||
|
||||
auto sub = [&](nb::module_ &m, const char *qualified, const char *name) {
|
||||
return make_exc(m, qualified, name, g_base);
|
||||
};
|
||||
|
||||
#define OCP_EXC(modvar, modname, name) \
|
||||
sub(modvar, "OCP." #modname "." #name, #name)
|
||||
OCP_EXC(std_mod, Standard, Standard_DomainError);
|
||||
OCP_EXC(std_mod, Standard, Standard_RangeError);
|
||||
OCP_EXC(std_mod, Standard, Standard_OutOfRange);
|
||||
OCP_EXC(std_mod, Standard, Standard_NoSuchObject);
|
||||
OCP_EXC(std_mod, Standard, Standard_TypeMismatch);
|
||||
OCP_EXC(std_mod, Standard, Standard_NullObject);
|
||||
OCP_EXC(std_mod, Standard, Standard_NullValue);
|
||||
OCP_EXC(std_mod, Standard, Standard_ConstructionError);
|
||||
OCP_EXC(std_mod, Standard, Standard_DimensionError);
|
||||
OCP_EXC(std_mod, Standard, Standard_DimensionMismatch);
|
||||
OCP_EXC(std_mod, Standard, Standard_NumericError);
|
||||
OCP_EXC(std_mod, Standard, Standard_DivideByZero);
|
||||
OCP_EXC(std_mod, Standard, Standard_Overflow);
|
||||
OCP_EXC(std_mod, Standard, Standard_Underflow);
|
||||
OCP_EXC(std_mod, Standard, Standard_ProgramError);
|
||||
OCP_EXC(std_mod, Standard, Standard_NotImplemented);
|
||||
OCP_EXC(std_mod, Standard, Standard_ImmutableObject);
|
||||
OCP_EXC(fail_mod, StdFail, StdFail_NotDone);
|
||||
OCP_EXC(fail_mod, StdFail, StdFail_UndefinedDerivative);
|
||||
OCP_EXC(fail_mod, StdFail, StdFail_UndefinedValue);
|
||||
#undef OCP_EXC
|
||||
|
||||
nb::register_exception_translator(
|
||||
[](const std::exception_ptr &p, void *) {
|
||||
try {
|
||||
std::rethrow_exception(p);
|
||||
} catch (const Standard_Failure &e) {
|
||||
// Dispatch on the dynamic OCCT type so the Python type name
|
||||
// matches what the kernel actually threw; anything we did not
|
||||
// bind falls back to the RuntimeError-derived base.
|
||||
PyObject *type = g_base;
|
||||
const char *name = e.DynamicType()->Name();
|
||||
if (name) {
|
||||
auto it = g_exc_types.find(name);
|
||||
if (it != g_exc_types.end())
|
||||
type = it->second;
|
||||
}
|
||||
const char *msg = e.GetMessageString();
|
||||
std::string text = std::string(name ? name : "Standard_Failure");
|
||||
if (msg && *msg)
|
||||
text = std::string(msg);
|
||||
PyErr_SetString(type, text.c_str());
|
||||
}
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
20
src/common/occt_exceptions.h
Normal file
20
src/common/occt_exceptions.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
OCCT Standard_Failure -> Python exception translation.
|
||||
|
||||
The backend's discipline is `IsDone()` gates (47 sites) plus `except
|
||||
RuntimeError` (~10 sites); it never names an OCCT exception class. So the
|
||||
hard requirement is that every kernel failure arrives as something deriving
|
||||
RuntimeError. The concrete type names matter too, but for a subtler
|
||||
reason: cad_pool's worker children marshal failures home as
|
||||
f"{type(exc).__name__}: {exc}" strings, so the name is observable output.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
/// Create the OCP.Standard / OCP.StdFail exception classes and install the
|
||||
/// translator. Call once, before any module that can raise.
|
||||
void register_occt_exceptions(nb::module_ &root);
|
||||
124
src/common/occt_handle.h
Normal file
124
src/common/occt_handle.h
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Type caster for opencascade::handle<T> — the ownership core of this binding.
|
||||
|
||||
OCCT transients carry their own intrusive, atomic reference count
|
||||
(Standard_Transient), so a handle keeps an object alive entirely on the C++
|
||||
side. That is what lets us release the GIL around kernel calls: OCCT may
|
||||
copy handles on its own worker threads without ever touching Python.
|
||||
|
||||
The design mirrors nanobind's own stl/shared_ptr.h caster:
|
||||
|
||||
C++ -> Python the wrapper is a *non-owning* nanobind instance pointing
|
||||
at the C++ object, plus one handle stored in the object's
|
||||
keep-alive list. Python holds exactly one OCCT reference
|
||||
per wrapper, released at deallocation. An existing
|
||||
wrapper is reused (is_new == false), so `a is b` holds for
|
||||
as long as a wrapper stays alive.
|
||||
|
||||
Python -> C++ a plain handle copy (one incref), balanced when the caster
|
||||
dies after the call. Unlike shared_ptr we do *not* need
|
||||
to keep the PyObject alive: the OCCT refcount, not the
|
||||
Python instance, owns the object's memory.
|
||||
|
||||
That last sentence is only true if every wrapped transient was heap
|
||||
allocated and is handle-owned. See occt_transient.h — transient
|
||||
constructors are bound through OCP_TRANSIENT_NEW, never nb::init<>, so
|
||||
Python never owns transient storage. from_python re-checks the invariant
|
||||
rather than trusting it, because the failure mode is a double free.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
#include <Standard_Handle.hxx>
|
||||
#include <Standard_Transient.hxx>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
NAMESPACE_BEGIN(NB_NAMESPACE)
|
||||
NAMESPACE_BEGIN(detail)
|
||||
|
||||
// Marked NB_NOINLINE so the (identical) body is not duplicated into every
|
||||
// instantiation of the caster below — there is one per bound transient class.
|
||||
inline NB_NOINLINE void
|
||||
occt_handle_keep_alive(opencascade::handle<Standard_Transient> &&h,
|
||||
PyObject *o) noexcept {
|
||||
keep_alive(o, new opencascade::handle<Standard_Transient>(std::move(h)),
|
||||
[](void *p) noexcept {
|
||||
delete (opencascade::handle<Standard_Transient> *) p;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct type_caster<
|
||||
opencascade::handle<T>,
|
||||
enable_if_t<std::is_base_of_v<Standard_Transient, std::decay_t<T>>>> {
|
||||
static constexpr bool IsClass = true;
|
||||
using Caster = make_caster<T>;
|
||||
using Td = std::decay_t<T>;
|
||||
|
||||
NB_TYPE_CASTER(opencascade::handle<T>, Caster::Name)
|
||||
|
||||
static_assert(is_base_caster_v<Caster>,
|
||||
"Conversion of opencascade::handle<T> requires that T is "
|
||||
"bound through nanobind's regular class mechanism.");
|
||||
|
||||
bool from_python(handle src, uint8_t flags,
|
||||
cleanup_list *cleanup) noexcept {
|
||||
// A null handle is OCCT's absent value and maps to None in both
|
||||
// directions; several APIs (BRep_Tool::Surface on a degenerate edge,
|
||||
// for one) legitimately return one.
|
||||
if (src.is_none()) {
|
||||
value = Value();
|
||||
return true;
|
||||
}
|
||||
|
||||
flags &= ~((uint8_t) cast_flags::convert);
|
||||
|
||||
Caster caster;
|
||||
if (!caster.from_python(src, flags, cleanup))
|
||||
return false;
|
||||
|
||||
Td *ptr = caster.operator Td *();
|
||||
|
||||
// Invariant check, not defensive programming: a refcount of zero means
|
||||
// this wrapper owns its storage (nb::init<>, or a by-value return),
|
||||
// and taking a handle to it would hand OCCT the right to `delete` a
|
||||
// nanobind instance's memory. Refuse instead — a TypeError beats a
|
||||
// heap corruption, and it can only be reached by a binding bug.
|
||||
if (ptr && ptr->GetRefCount() == 0) {
|
||||
assert(!"OCCT transient is not handle-owned (see occt_transient.h)");
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Value(ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static handle from_cpp(const Value &v, rv_policy,
|
||||
cleanup_list *cleanup) noexcept {
|
||||
Td *ptr = v.get();
|
||||
if (!ptr)
|
||||
return none().release();
|
||||
|
||||
// Transients are polymorphic, so nb_type_put_p downcasts on the
|
||||
// dynamic type: BRep_Tool::Surface returning a Geom_Surface handle to
|
||||
// a plane arrives in Python as Geom_Plane when that class is bound.
|
||||
bool is_new = false;
|
||||
handle result = nb_type_put_p(&typeid(Td), &typeid(*ptr), (void *) ptr,
|
||||
rv_policy::reference, cleanup, &is_new);
|
||||
|
||||
// Only a freshly created wrapper takes a reference. Attaching one per
|
||||
// conversion would pile up redundant handles on a long-lived object
|
||||
// that crosses the boundary many times.
|
||||
if (result.is_valid() && is_new)
|
||||
occt_handle_keep_alive(
|
||||
opencascade::handle<Standard_Transient>(ptr), result.ptr());
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(detail)
|
||||
NAMESPACE_END(NB_NAMESPACE)
|
||||
28
src/common/occt_module.cpp
Normal file
28
src/common/occt_module.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "occt_module.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
std::vector<std::string> &ocp_module_registry() {
|
||||
static std::vector<std::string> registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
nb::module_ ocp_named_module(const char *full_name) {
|
||||
// Borrowed reference, and already interned in sys.modules — which is what
|
||||
// makes `import OCP.TopoDS` work without a .py shim per module. Note this
|
||||
// does not require the parent package to exist yet.
|
||||
PyObject *mod = PyImport_AddModule(full_name);
|
||||
if (!mod)
|
||||
throw nb::python_error();
|
||||
return nb::borrow<nb::module_>(mod);
|
||||
}
|
||||
|
||||
nb::module_ ocp_submodule(nb::module_ &parent, const char *name) {
|
||||
nb::module_ m = ocp_named_module((std::string("OCP.") + name).c_str());
|
||||
parent.attr(name) = m;
|
||||
|
||||
auto ® = ocp_module_registry();
|
||||
if (std::find(reg.begin(), reg.end(), name) == reg.end())
|
||||
reg.emplace_back(name);
|
||||
return m;
|
||||
}
|
||||
49
src/common/occt_module.h
Normal file
49
src/common/occt_module.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Submodule and static-method helpers.
|
||||
|
||||
The wheel ships one extension, OCP/_OCP, which registers the OCP.*
|
||||
submodules itself. They are created with PyImport_AddModule so they are
|
||||
real entries in sys.modules: `import OCP.TopoDS`, `from OCP.TopoDS import
|
||||
TopoDS_Shape` and cls.__module__ == "OCP.TopoDS" all work without a shim
|
||||
module per name. (nanobind has no def_submodule.)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
// Included here, not per module, so no translation unit can accidentally bind
|
||||
// a handle-returning API without the caster in scope: the symptom is a silent
|
||||
// fallback that reports "unable to convert" only at call time.
|
||||
#include "occt_handle.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
// Every module file wants "name"_a for keyword arguments.
|
||||
using namespace nb::literals;
|
||||
|
||||
/// Names of every OCP.* submodule registered so far, in registration order.
|
||||
/// OCP/__init__.py re-exports these so `OCP.TopoDS` resolves as an attribute
|
||||
/// too, not only as an import.
|
||||
std::vector<std::string> &ocp_module_registry();
|
||||
|
||||
/// Create (or fetch) a real module by its full dotted name. Idempotent, and
|
||||
/// deliberately does not require the parent package to exist — n3xd_ocp's
|
||||
/// submodules are registered this way so that the Python package's __init__
|
||||
/// still runs whichever import happens first.
|
||||
nb::module_ ocp_named_module(const char *full_name);
|
||||
|
||||
/// Create (or fetch) "OCP.<name>", attach it to `parent`, and record it for
|
||||
/// OCP/__init__.py's re-export loop.
|
||||
nb::module_ ocp_submodule(nb::module_ &parent, const char *name);
|
||||
|
||||
/// Bind an OCCT static method under the `_s` suffix.
|
||||
///
|
||||
/// Upstream OCP suffixes every static it exposes, and the app calls 176 of
|
||||
/// them by that name (BRepGProp.SurfaceProperties_s, TopExp.MapShapes_s,
|
||||
/// BinTools.Write_s, ...). The rule here is blanket rather than clash-driven,
|
||||
/// so no static can accidentally ship without it.
|
||||
#define OCP_DEF_S(cls, name, ...) (cls).def_static(name "_s", __VA_ARGS__)
|
||||
35
src/common/occt_policies.h
Normal file
35
src/common/occt_policies.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Return-value and GIL policies.
|
||||
|
||||
Two rules live here, and both close a defect class the app currently works
|
||||
around in Python:
|
||||
|
||||
1. Sub-shapes come back BY VALUE. A TopoDS_Shape is a small value holding
|
||||
a handle to its TShape, so copying it is one incref and the copy owns
|
||||
what it points at. Returning a reference into a container (an explorer's
|
||||
Current(), a map's FindKey(), a BOP history list) instead lets the
|
||||
wrapper outlive its owner — that is what segfaulted a process-global
|
||||
face memo under upstream OCP, and why cad/topology/geom_memo.py is
|
||||
scoped to a single rebuild by a ContextVar.
|
||||
|
||||
nanobind copies lvalue references by default; OCP_RETURN_COPY states it
|
||||
explicitly so a reviewer can see the guarantee at the call site.
|
||||
|
||||
2. The GIL is released around calls that stay inside the kernel. The list
|
||||
is deliberate rather than blanket: a call that can re-enter Python (the
|
||||
BinTools stream bridge, notably) must keep it.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
/// Explicit "returns an owned copy" policy for shape-valued returns.
|
||||
#define OCP_RETURN_COPY nb::rv_policy::copy
|
||||
|
||||
/// Release the GIL for the duration of a kernel call. Only for calls that
|
||||
/// cannot re-enter the interpreter: Build/Perform, meshing, BRepCheck,
|
||||
/// file-based readers and writers.
|
||||
#define OCP_NOGIL nb::call_guard<nb::gil_scoped_release>()
|
||||
41
src/common/occt_stream.h
Normal file
41
src/common/occt_stream.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Python file-like <-> std::stream adaptation for BinTools.
|
||||
|
||||
Upstream's signature takes an io.BytesIO, and the app always passes one
|
||||
(cad_pool and derive.py serialise shapes through BytesIO). Rather than
|
||||
bridge a streambuf that calls back into Python on every chunk, this slurps:
|
||||
read the whole payload first, then hand the kernel a pure C++ stream.
|
||||
|
||||
Two things fall out of that. It is correct no matter how BinTools seeks
|
||||
inside the stream, and the kernel half touches no Python at all — so the
|
||||
GIL can be released around it, which a callback bridge could not do. The
|
||||
cost is one extra copy of the BREP bytes, which n3xd_ocp.bintools avoids
|
||||
entirely for the pool paths that care.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
/// Read a Python file-like object to end of stream.
|
||||
inline std::string ocp_slurp(nb::handle stream) {
|
||||
nb::object data = nb::getattr(stream, "read")();
|
||||
char *buf = nullptr;
|
||||
Py_ssize_t size = 0;
|
||||
if (PyBytes_AsStringAndSize(data.ptr(), &buf, &size) != 0)
|
||||
throw nb::python_error();
|
||||
return std::string(buf, (size_t) size);
|
||||
}
|
||||
|
||||
/// Write a byte payload to a Python file-like object.
|
||||
inline void ocp_spit(nb::handle stream, const std::string &data) {
|
||||
nb::object payload = nb::steal(
|
||||
PyBytes_FromStringAndSize(data.data(), (Py_ssize_t) data.size()));
|
||||
if (!payload.is_valid())
|
||||
throw nb::python_error();
|
||||
nb::getattr(stream, "write")(payload);
|
||||
}
|
||||
42
src/common/occt_transient.h
Normal file
42
src/common/occt_transient.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Binding helpers for OCCT transient (handle-managed) classes.
|
||||
|
||||
Rule: a transient class never gets nb::init<>. nanobind's normal
|
||||
constructor placement-news the object into the Python instance's own
|
||||
storage, which OCCT would later try to `delete` when the last handle goes
|
||||
away. The app hits this on a real path — sketch_builder/edges.py builds a
|
||||
Geom_BSplineCurve in Python and hands it to BRepBuilderAPI_MakeEdge, which
|
||||
stores a handle to it that outlives the call.
|
||||
|
||||
So constructors are bound through nb::new_ returning a handle: the object
|
||||
is heap allocated and reaches Python through the handle caster, owning its
|
||||
own storage from birth. occt_handle.h's from_python check enforces this.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
#include <Standard_Handle.hxx>
|
||||
#include <Standard_Transient.hxx>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
/// Declare a bound transient class. Bind its constructors with ocp_new<T,
|
||||
/// Args...>() — nb::init<> on such a class is a bug (see above).
|
||||
template <typename T, typename... Bases>
|
||||
nb::class_<T, Bases...> ocp_transient_class(nb::handle scope, const char *name) {
|
||||
return nb::class_<T, Bases...>(scope, name);
|
||||
}
|
||||
|
||||
/// Heap-allocating constructor for a transient class:
|
||||
///
|
||||
/// cls.def(ocp_new<Poly_Triangulation, int, int, bool>(),
|
||||
/// "nbNodes"_a, "nbTriangles"_a, "hasUVNodes"_a);
|
||||
template <typename T, typename... Args> auto ocp_new() {
|
||||
return nb::new_([](Args... args) {
|
||||
return opencascade::handle<T>(new T(std::forward<Args>(args)...));
|
||||
});
|
||||
}
|
||||
59
src/core.cpp
Normal file
59
src/core.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
OCP._OCP — the single extension module.
|
||||
|
||||
One .so registers every OCP.* submodule rather than shipping one extension
|
||||
per module: the whole surface traffics in TopoDS_Shape, gp_* and handles,
|
||||
so sharing types in-process is free here and would otherwise depend on
|
||||
nanobind's cross-extension registry; registration order stays an explicit
|
||||
sequence below instead of an import-order puzzle; and cad_pool's forkserver
|
||||
warms exactly one dlopen. Per-module .cpp files keep incremental compiles
|
||||
cheap — only the final link is shared.
|
||||
*/
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
#include <nanobind/stl/string.h>
|
||||
#include <nanobind/stl/vector.h>
|
||||
|
||||
#include "common/occt_exceptions.h"
|
||||
#include "common/occt_module.h"
|
||||
|
||||
#include <Standard_Version.hxx>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
void register_Standard(nb::module_ &);
|
||||
void register_gp(nb::module_ &);
|
||||
void register_TopAbs(nb::module_ &);
|
||||
void register_TopLoc(nb::module_ &);
|
||||
void register_TopoDS(nb::module_ &);
|
||||
void register_TopTools(nb::module_ &);
|
||||
void register_TopExp(nb::module_ &);
|
||||
void register_Poly(nb::module_ &);
|
||||
void register_BRep(nb::module_ &);
|
||||
void register_BinTools(nb::module_ &);
|
||||
void register_ext(nb::module_ &);
|
||||
|
||||
NB_MODULE(_OCP, m) {
|
||||
m.doc() = "nanobind bindings for the OpenCASCADE geometry kernel";
|
||||
|
||||
// Exceptions first: they create OCP.Standard and OCP.StdFail, and any
|
||||
// module registered afterwards may raise through the translator.
|
||||
register_occt_exceptions(m);
|
||||
|
||||
register_Standard(m);
|
||||
register_gp(m);
|
||||
register_TopAbs(m);
|
||||
register_TopLoc(m);
|
||||
register_TopoDS(m);
|
||||
register_TopTools(m);
|
||||
register_TopExp(m);
|
||||
register_Poly(m);
|
||||
register_BRep(m);
|
||||
register_BinTools(m);
|
||||
|
||||
register_ext(m);
|
||||
|
||||
m.attr("__all_modules__") = ocp_module_registry();
|
||||
m.attr("__occt_version__") = OCC_VERSION_COMPLETE;
|
||||
m.attr("__version__") = N3XD_OCP_VERSION;
|
||||
}
|
||||
53
src/ext/ext_bintools.cpp
Normal file
53
src/ext/ext_bintools.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
n3xd_ocp.bintools — shape <-> bytes without the file-object detour.
|
||||
|
||||
The pools move every shape as BinTools bytes through a BytesIO purely
|
||||
because that is the shape of upstream's API. Here the payload is a bytes
|
||||
object directly: one buffer instead of a BytesIO plus its internal copy,
|
||||
and the kernel half runs with the GIL released.
|
||||
|
||||
Byte-for-byte identical to OCP.BinTools output — same BinTools::Write, same
|
||||
format version — so it is interchangeable with it, including for the
|
||||
content-addressed derive payloads.
|
||||
*/
|
||||
|
||||
#include "../common/occt_module.h"
|
||||
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
#include <BinTools.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
void register_ext_bintools() {
|
||||
nb::module_ m = ocp_named_module("n3xd_ocp.bintools");
|
||||
|
||||
m.def(
|
||||
"write_bytes",
|
||||
[](const TopoDS_Shape &shape) {
|
||||
std::ostringstream out;
|
||||
{
|
||||
nb::gil_scoped_release nogil;
|
||||
BinTools::Write(shape, out);
|
||||
}
|
||||
std::string data = out.str();
|
||||
return nb::bytes(data.data(), data.size());
|
||||
},
|
||||
"shape"_a,
|
||||
"Serialise a shape to BREP bytes (identical to OCP.BinTools.Write_s).");
|
||||
|
||||
m.def(
|
||||
"read_bytes",
|
||||
[](nb::bytes data) {
|
||||
std::string buf(data.c_str(), data.size());
|
||||
TopoDS_Shape shape;
|
||||
{
|
||||
nb::gil_scoped_release nogil;
|
||||
std::istringstream in(buf);
|
||||
BinTools::Read(shape, in);
|
||||
}
|
||||
return shape;
|
||||
},
|
||||
"data"_a, "Deserialise BREP bytes into a shape.");
|
||||
}
|
||||
25
src/ext/ext_core.cpp
Normal file
25
src/ext/ext_core.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
n3xd_ocp — the extension surface, kept out of the OCP namespace on purpose.
|
||||
|
||||
OCP.* stays a drop-in replacement for cadquery-ocp-novtk, symbol for symbol,
|
||||
so parity testing means something. Anything that goes *beyond* upstream —
|
||||
bulk array extraction, batched measurement, GIL-free serialisation — lives
|
||||
here instead, in the same wheel. The backend adopts these after the
|
||||
cutover, one call site at a time.
|
||||
|
||||
Only the leaf modules are registered here. Creating the `n3xd_ocp` parent
|
||||
from C++ would put a bare module in sys.modules, and a later `import
|
||||
n3xd_ocp` would then find it and never run the package's __init__.py.
|
||||
*/
|
||||
|
||||
#include "../common/occt_module.h"
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
void register_ext_bintools();
|
||||
void register_ext_debug();
|
||||
|
||||
void register_ext(nb::module_ &) {
|
||||
register_ext_bintools();
|
||||
register_ext_debug();
|
||||
}
|
||||
78
src/ext/ext_debug.cpp
Normal file
78
src/ext/ext_debug.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
n3xd_ocp._debug — introspection the handle-model tests need.
|
||||
|
||||
Not part of the supported surface; it exists so tests can assert ownership
|
||||
invariants (OCCT refcounts, wrapper identity) that are otherwise invisible
|
||||
from Python.
|
||||
*/
|
||||
|
||||
#include "../common/occt_handle.h"
|
||||
#include "../common/occt_module.h"
|
||||
|
||||
#include <Poly_Triangulation.hxx>
|
||||
#include <Standard_Transient.hxx>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
/// Holds handles alive on the C++ side so a test can drop every Python
|
||||
/// reference and check the object survived.
|
||||
std::vector<opencascade::handle<Standard_Transient>> &cpp_holds() {
|
||||
static std::vector<opencascade::handle<Standard_Transient>> held;
|
||||
return held;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_ext_debug() {
|
||||
nb::module_ m = ocp_named_module("n3xd_ocp._debug");
|
||||
|
||||
m.def(
|
||||
"refcount",
|
||||
[](const opencascade::handle<Standard_Transient> &h) {
|
||||
// The caster's own handle is alive for the duration of the call,
|
||||
// so subtract it to report what the caller actually holds.
|
||||
return h.IsNull() ? 0 : h->GetRefCount() - 1;
|
||||
},
|
||||
nb::arg("obj").none(),
|
||||
"OCCT reference count excluding this call's own handle.");
|
||||
|
||||
m.def(
|
||||
"roundtrip",
|
||||
[](const opencascade::handle<Standard_Transient> &h) { return h; },
|
||||
nb::arg("obj").none(),
|
||||
"Send a transient through C++ and back — must return the same wrapper.");
|
||||
|
||||
m.def(
|
||||
"hold",
|
||||
[](const opencascade::handle<Standard_Transient> &h) {
|
||||
cpp_holds().push_back(h);
|
||||
return (int) cpp_holds().size() - 1;
|
||||
},
|
||||
"obj"_a, "Keep a C++-side reference; returns its slot index.");
|
||||
|
||||
m.def(
|
||||
"release_held", []() { cpp_holds().clear(); },
|
||||
"Drop every C++-side reference taken by hold().");
|
||||
|
||||
m.def(
|
||||
"held_is_alive",
|
||||
[](int slot) {
|
||||
auto &held = cpp_holds();
|
||||
return slot >= 0 && slot < (int) held.size() &&
|
||||
!held[(size_t) slot].IsNull();
|
||||
},
|
||||
"slot"_a);
|
||||
|
||||
m.def(
|
||||
"held_nb_nodes",
|
||||
[](int slot) {
|
||||
auto &held = cpp_holds();
|
||||
opencascade::handle<Poly_Triangulation> tri =
|
||||
opencascade::handle<Poly_Triangulation>::DownCast(
|
||||
held[(size_t) slot]);
|
||||
return tri.IsNull() ? -1 : tri->NbNodes();
|
||||
},
|
||||
"slot"_a, "Touch a held object's memory — catches a premature free.");
|
||||
}
|
||||
73
src/modules/mod_BRep.cpp
Normal file
73
src/modules/mod_BRep.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
OCP.BRep — builder plus the Inc 0 half of BRep_Tool.
|
||||
|
||||
BRep_Tool::Surface / Curve return Geom handles and land with Inc 1, where
|
||||
the Geom classes arrive; Triangulation is here because it is what the
|
||||
tessellation path reads and what the handle spike exercises.
|
||||
*/
|
||||
|
||||
#include "../common/occt_module.h"
|
||||
#include "../common/occt_policies.h"
|
||||
|
||||
#include <BRep_Builder.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <Poly_Triangulation.hxx>
|
||||
#include <TopLoc_Location.hxx>
|
||||
#include <TopoDS_Compound.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopoDS_Shell.hxx>
|
||||
#include <TopoDS_Solid.hxx>
|
||||
#include <TopoDS_Vertex.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
|
||||
void register_BRep(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "BRep");
|
||||
|
||||
nb::class_<BRep_Builder>(m, "BRep_Builder")
|
||||
.def(nb::init<>())
|
||||
.def("MakeCompound", &BRep_Builder::MakeCompound, "C"_a)
|
||||
.def("MakeShell", &BRep_Builder::MakeShell, "S"_a)
|
||||
.def("MakeSolid", &BRep_Builder::MakeSolid, "S"_a)
|
||||
.def("MakeWire", &BRep_Builder::MakeWire, "W"_a)
|
||||
.def("Add",
|
||||
[](const BRep_Builder &self, TopoDS_Shape &S,
|
||||
const TopoDS_Shape &C) { self.Add(S, C); },
|
||||
"S"_a, "C"_a)
|
||||
.def("Remove",
|
||||
[](const BRep_Builder &self, TopoDS_Shape &S,
|
||||
const TopoDS_Shape &C) { self.Remove(S, C); },
|
||||
"S"_a, "C"_a);
|
||||
|
||||
nb::class_<BRep_Tool> cls(m, "BRep_Tool");
|
||||
|
||||
// The location is an in/out parameter, matching upstream and the five call
|
||||
// sites in the app (`BRep_Tool.Triangulation_s(face, loc)`): nanobind hands
|
||||
// the lambda a reference into the caller's own instance, so the assignment
|
||||
// is visible in Python. A face without a mesh yields a null handle, which
|
||||
// the caster maps to None.
|
||||
OCP_DEF_S(
|
||||
cls, "Triangulation",
|
||||
[](const TopoDS_Face &F, TopLoc_Location &L) {
|
||||
return BRep_Tool::Triangulation(F, L);
|
||||
},
|
||||
"F"_a, "L"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "Pnt", [](const TopoDS_Vertex &V) { return BRep_Tool::Pnt(V); },
|
||||
"V"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "Degenerated",
|
||||
[](const TopoDS_Edge &E) { return BRep_Tool::Degenerated(E); }, "E"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "Tolerance",
|
||||
[](const TopoDS_Face &F) { return BRep_Tool::Tolerance(F); }, "F"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "Tolerance",
|
||||
[](const TopoDS_Edge &E) { return BRep_Tool::Tolerance(E); }, "E"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "Tolerance",
|
||||
[](const TopoDS_Vertex &V) { return BRep_Tool::Tolerance(V); }, "V"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "IsClosed",
|
||||
[](const TopoDS_Shape &S) { return BRep_Tool::IsClosed(S); }, "S"_a);
|
||||
}
|
||||
51
src/modules/mod_BinTools.cpp
Normal file
51
src/modules/mod_BinTools.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
OCP.BinTools — the IPC-critical path.
|
||||
|
||||
Every subprocess pool serialises shapes through here, and derive.py content-
|
||||
addresses the result (payloads/derived/brep/<sha256>.brep) with the digest
|
||||
stored in the document. So byte-stability is a correctness requirement,
|
||||
not merely a round-trip one, and it is what the Inc 0 gate measures.
|
||||
|
||||
The GIL is released around the kernel half. That is possible because the
|
||||
Python file object is drained (or filled) on either side of the call rather
|
||||
than bridged through a streambuf that would call back into the interpreter
|
||||
mid-serialisation — see occt_stream.h.
|
||||
*/
|
||||
|
||||
#include "../common/occt_module.h"
|
||||
#include "../common/occt_stream.h"
|
||||
|
||||
#include <BinTools.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
void register_BinTools(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "BinTools");
|
||||
|
||||
nb::class_<BinTools> cls(m, "BinTools");
|
||||
|
||||
// Matches upstream overload 1: writes with triangulation, at
|
||||
// BinTools_FormatVersion_CURRENT.
|
||||
OCP_DEF_S(
|
||||
cls, "Write",
|
||||
[](const TopoDS_Shape &theShape, nb::object theStream) {
|
||||
std::ostringstream out;
|
||||
{
|
||||
nb::gil_scoped_release nogil;
|
||||
BinTools::Write(theShape, out);
|
||||
}
|
||||
ocp_spit(theStream, out.str());
|
||||
},
|
||||
"theShape"_a, "theStream"_a);
|
||||
|
||||
OCP_DEF_S(
|
||||
cls, "Read",
|
||||
[](TopoDS_Shape &theShape, nb::object theStream) {
|
||||
std::string data = ocp_slurp(theStream);
|
||||
std::istringstream in(data);
|
||||
nb::gil_scoped_release nogil;
|
||||
BinTools::Read(theShape, in);
|
||||
},
|
||||
"theShape"_a, "theStream"_a);
|
||||
}
|
||||
56
src/modules/mod_Poly.cpp
Normal file
56
src/modules/mod_Poly.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
OCP.Poly — also the handle-model spike vehicle.
|
||||
|
||||
Poly_Triangulation is a transient (handle-managed) class that the app both
|
||||
receives from the kernel (BRep_Tool::Triangulation) and, in the wider
|
||||
surface, constructs. It is therefore the natural place to prove the
|
||||
ownership rules in occt_handle.h before the rest of the surface depends on
|
||||
them. Constructors go through ocp_new, never nb::init<>.
|
||||
*/
|
||||
|
||||
#include "../common/occt_module.h"
|
||||
#include "../common/occt_policies.h"
|
||||
#include "../common/occt_transient.h"
|
||||
|
||||
#include <Poly_Triangulation.hxx>
|
||||
|
||||
void register_Poly(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "Poly");
|
||||
|
||||
nb::class_<Poly_Triangle>(m, "Poly_Triangle")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<Standard_Integer, Standard_Integer, Standard_Integer>(),
|
||||
"theN1"_a, "theN2"_a, "theN3"_a)
|
||||
.def("Value", &Poly_Triangle::Value, "theIndex"_a)
|
||||
.def("Get", [](const Poly_Triangle &t) {
|
||||
Standard_Integer n1 = 0, n2 = 0, n3 = 0;
|
||||
t.Get(n1, n2, n3);
|
||||
return nb::make_tuple(n1, n2, n3);
|
||||
});
|
||||
|
||||
nb::class_<Poly_Triangulation, Standard_Transient>(m, "Poly_Triangulation")
|
||||
.def(ocp_new<Poly_Triangulation>())
|
||||
.def(ocp_new<Poly_Triangulation, Standard_Integer, Standard_Integer,
|
||||
Standard_Boolean, Standard_Boolean>(),
|
||||
"theNbNodes"_a, "theNbTriangles"_a, "theHasUVNodes"_a,
|
||||
"theHasNormals"_a = Standard_False)
|
||||
.def("NbNodes", &Poly_Triangulation::NbNodes)
|
||||
.def("NbTriangles", &Poly_Triangulation::NbTriangles)
|
||||
.def("HasUVNodes", &Poly_Triangulation::HasUVNodes)
|
||||
.def("HasNormals", &Poly_Triangulation::HasNormals)
|
||||
.def("Node", &Poly_Triangulation::Node, "theIndex"_a, OCP_RETURN_COPY)
|
||||
.def("SetNode", &Poly_Triangulation::SetNode, "theIndex"_a,
|
||||
"thePnt"_a)
|
||||
.def("Triangle", &Poly_Triangulation::Triangle, "theIndex"_a,
|
||||
OCP_RETURN_COPY)
|
||||
.def("SetTriangle", &Poly_Triangulation::SetTriangle, "theIndex"_a,
|
||||
"theTriangle"_a)
|
||||
.def("Normal",
|
||||
nb::overload_cast<Standard_Integer>(&Poly_Triangulation::Normal,
|
||||
nb::const_),
|
||||
"theIndex"_a, OCP_RETURN_COPY)
|
||||
.def("UVNode", &Poly_Triangulation::UVNode, "theIndex"_a,
|
||||
OCP_RETURN_COPY)
|
||||
.def("Deflection",
|
||||
nb::overload_cast<>(&Poly_Triangulation::Deflection, nb::const_));
|
||||
}
|
||||
25
src/modules/mod_Standard.cpp
Normal file
25
src/modules/mod_Standard.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "../common/occt_module.h"
|
||||
|
||||
#include <Standard_Transient.hxx>
|
||||
#include <Standard_Type.hxx>
|
||||
#include <Standard_Version.hxx>
|
||||
|
||||
void register_Standard(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "Standard");
|
||||
|
||||
// Every handle-managed class derives from this, and nanobind needs it
|
||||
// registered before it can be named as a base. Deliberately not
|
||||
// constructible from Python: transients are created through ocp_new (see
|
||||
// occt_transient.h), never by placement-new into instance storage.
|
||||
nb::class_<Standard_Transient>(m, "Standard_Transient")
|
||||
.def("GetRefCount", &Standard_Transient::GetRefCount)
|
||||
.def("IsInstance",
|
||||
[](const Standard_Transient &self, const char *name) {
|
||||
return self.IsKind(name);
|
||||
})
|
||||
.def("DynamicType", [](const Standard_Transient &self) {
|
||||
return std::string(self.DynamicType()->Name());
|
||||
});
|
||||
|
||||
m.attr("OCC_VERSION_COMPLETE") = OCC_VERSION_COMPLETE;
|
||||
}
|
||||
44
src/modules/mod_TopAbs.cpp
Normal file
44
src/modules/mod_TopAbs.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "../common/occt_module.h"
|
||||
|
||||
#include <TopAbs.hxx>
|
||||
#include <TopAbs_Orientation.hxx>
|
||||
#include <TopAbs_ShapeEnum.hxx>
|
||||
#include <TopAbs_State.hxx>
|
||||
|
||||
void register_TopAbs(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "TopAbs");
|
||||
|
||||
// is_arithmetic + export_values reproduce the pybind11 enum behaviour the
|
||||
// app relies on: members at module scope (`from OCP.TopAbs import
|
||||
// TopAbs_FACE`) and int comparison.
|
||||
nb::enum_<TopAbs_ShapeEnum>(m, "TopAbs_ShapeEnum", nb::is_arithmetic())
|
||||
.value("TopAbs_COMPOUND", TopAbs_COMPOUND)
|
||||
.value("TopAbs_COMPSOLID", TopAbs_COMPSOLID)
|
||||
.value("TopAbs_SOLID", TopAbs_SOLID)
|
||||
.value("TopAbs_SHELL", TopAbs_SHELL)
|
||||
.value("TopAbs_FACE", TopAbs_FACE)
|
||||
.value("TopAbs_WIRE", TopAbs_WIRE)
|
||||
.value("TopAbs_EDGE", TopAbs_EDGE)
|
||||
.value("TopAbs_VERTEX", TopAbs_VERTEX)
|
||||
.value("TopAbs_SHAPE", TopAbs_SHAPE)
|
||||
.export_values();
|
||||
|
||||
nb::enum_<TopAbs_Orientation>(m, "TopAbs_Orientation", nb::is_arithmetic())
|
||||
.value("TopAbs_FORWARD", TopAbs_FORWARD)
|
||||
.value("TopAbs_REVERSED", TopAbs_REVERSED)
|
||||
.value("TopAbs_INTERNAL", TopAbs_INTERNAL)
|
||||
.value("TopAbs_EXTERNAL", TopAbs_EXTERNAL)
|
||||
.export_values();
|
||||
|
||||
nb::enum_<TopAbs_State>(m, "TopAbs_State", nb::is_arithmetic())
|
||||
.value("TopAbs_IN", TopAbs_IN)
|
||||
.value("TopAbs_OUT", TopAbs_OUT)
|
||||
.value("TopAbs_ON", TopAbs_ON)
|
||||
.value("TopAbs_UNKNOWN", TopAbs_UNKNOWN)
|
||||
.export_values();
|
||||
|
||||
nb::class_<TopAbs> cls(m, "TopAbs");
|
||||
OCP_DEF_S(cls, "Compose", &TopAbs::Compose, "Or1"_a, "Or2"_a);
|
||||
OCP_DEF_S(cls, "Reverse", &TopAbs::Reverse, "Or"_a);
|
||||
OCP_DEF_S(cls, "Complement", &TopAbs::Complement, "Or"_a);
|
||||
}
|
||||
48
src/modules/mod_TopExp.cpp
Normal file
48
src/modules/mod_TopExp.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "../common/occt_module.h"
|
||||
#include "../common/occt_policies.h"
|
||||
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
||||
#include <TopTools_IndexedMapOfShape.hxx>
|
||||
|
||||
void register_TopExp(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "TopExp");
|
||||
|
||||
nb::class_<TopExp_Explorer>(m, "TopExp_Explorer")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<const TopoDS_Shape &, TopAbs_ShapeEnum,
|
||||
TopAbs_ShapeEnum>(),
|
||||
"S"_a, "ToFind"_a, "ToAvoid"_a = TopAbs_SHAPE)
|
||||
.def("Init", &TopExp_Explorer::Init, "S"_a, "ToFind"_a,
|
||||
"ToAvoid"_a = TopAbs_SHAPE)
|
||||
.def("More", &TopExp_Explorer::More)
|
||||
.def("Next", &TopExp_Explorer::Next)
|
||||
.def("Current", &TopExp_Explorer::Current, OCP_RETURN_COPY)
|
||||
.def("Value", &TopExp_Explorer::Value, OCP_RETURN_COPY)
|
||||
.def("ReInit", &TopExp_Explorer::ReInit)
|
||||
.def("Depth", &TopExp_Explorer::Depth)
|
||||
.def("Clear", &TopExp_Explorer::Clear);
|
||||
|
||||
nb::class_<TopExp> cls(m, "TopExp");
|
||||
|
||||
// MapShapes is the single most-called static in the backend (54 sites).
|
||||
OCP_DEF_S(
|
||||
cls, "MapShapes",
|
||||
[](const TopoDS_Shape &S, TopAbs_ShapeEnum T,
|
||||
TopTools_IndexedMapOfShape &M) { TopExp::MapShapes(S, T, M); },
|
||||
"S"_a, "T"_a, "M"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "MapShapes",
|
||||
[](const TopoDS_Shape &S, TopTools_IndexedMapOfShape &M) {
|
||||
TopExp::MapShapes(S, M);
|
||||
},
|
||||
"S"_a, "M"_a);
|
||||
OCP_DEF_S(
|
||||
cls, "MapShapesAndAncestors",
|
||||
[](const TopoDS_Shape &S, TopAbs_ShapeEnum TS, TopAbs_ShapeEnum TA,
|
||||
TopTools_IndexedDataMapOfShapeListOfShape &M) {
|
||||
TopExp::MapShapesAndAncestors(S, TS, TA, M);
|
||||
},
|
||||
"S"_a, "TS"_a, "TA"_a, "M"_a);
|
||||
}
|
||||
24
src/modules/mod_TopLoc.cpp
Normal file
24
src/modules/mod_TopLoc.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "../common/occt_module.h"
|
||||
#include "../common/occt_policies.h"
|
||||
|
||||
#include <TopLoc_Location.hxx>
|
||||
|
||||
void register_TopLoc(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "TopLoc");
|
||||
|
||||
nb::class_<TopLoc_Location>(m, "TopLoc_Location")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<const gp_Trsf &>(), "T"_a)
|
||||
.def("IsIdentity", &TopLoc_Location::IsIdentity)
|
||||
.def("Identity", &TopLoc_Location::Identity)
|
||||
.def("Transformation", &TopLoc_Location::Transformation,
|
||||
OCP_RETURN_COPY)
|
||||
.def("Inverted", &TopLoc_Location::Inverted, OCP_RETURN_COPY)
|
||||
.def("Multiplied", &TopLoc_Location::Multiplied, "Other"_a,
|
||||
OCP_RETURN_COPY)
|
||||
.def("Divided", &TopLoc_Location::Divided, "Other"_a, OCP_RETURN_COPY)
|
||||
.def("Predivided", &TopLoc_Location::Predivided, "Other"_a,
|
||||
OCP_RETURN_COPY)
|
||||
.def("IsEqual", &TopLoc_Location::IsEqual, "Other"_a)
|
||||
.def("IsDifferent", &TopLoc_Location::IsDifferent, "Other"_a);
|
||||
}
|
||||
100
src/modules/mod_TopTools.cpp
Normal file
100
src/modules/mod_TopTools.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
OCP.TopTools — the shape containers the topology code lives in.
|
||||
|
||||
Every accessor returns a shape by value (OCP_RETURN_COPY): a wrapper handed
|
||||
out by FindKey must not become a dangling view when the map is cleared or
|
||||
goes out of scope. build_entity_map_delta tests membership through
|
||||
IndexedMapOfShape::Contains (IsSame semantics), which is why Contains and
|
||||
FindIndex are bound rather than left to a Python-side scan.
|
||||
*/
|
||||
|
||||
#include "../common/occt_module.h"
|
||||
#include "../common/occt_policies.h"
|
||||
|
||||
#include <nanobind/make_iterator.h>
|
||||
|
||||
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
||||
#include <TopTools_IndexedMapOfShape.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
|
||||
void register_TopTools(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "TopTools");
|
||||
|
||||
nb::class_<TopTools_ListOfShape>(m, "TopTools_ListOfShape")
|
||||
.def(nb::init<>())
|
||||
.def("Append",
|
||||
[](TopTools_ListOfShape &self, const TopoDS_Shape &s) {
|
||||
self.Append(s);
|
||||
},
|
||||
"theItem"_a)
|
||||
.def("Prepend",
|
||||
[](TopTools_ListOfShape &self, const TopoDS_Shape &s) {
|
||||
self.Prepend(s);
|
||||
},
|
||||
"theItem"_a)
|
||||
.def("Extent", &TopTools_ListOfShape::Extent)
|
||||
.def("Size", &TopTools_ListOfShape::Size)
|
||||
.def("IsEmpty", &TopTools_ListOfShape::IsEmpty)
|
||||
.def("Clear", [](TopTools_ListOfShape &self) { self.Clear(); })
|
||||
.def("First",
|
||||
nb::overload_cast<>(&TopTools_ListOfShape::First, nb::const_),
|
||||
OCP_RETURN_COPY)
|
||||
.def("Last",
|
||||
nb::overload_cast<>(&TopTools_ListOfShape::Last, nb::const_),
|
||||
OCP_RETURN_COPY)
|
||||
.def("__len__", &TopTools_ListOfShape::Size)
|
||||
.def(
|
||||
"__iter__",
|
||||
[](const TopTools_ListOfShape &self) {
|
||||
return nb::make_iterator<nb::rv_policy::copy>(
|
||||
nb::type<TopTools_ListOfShape>(), "ListOfShapeIterator",
|
||||
self.begin(), self.end());
|
||||
},
|
||||
nb::keep_alive<0, 1>());
|
||||
|
||||
nb::class_<TopTools_IndexedMapOfShape>(m, "TopTools_IndexedMapOfShape")
|
||||
.def(nb::init<>())
|
||||
.def("Add",
|
||||
[](TopTools_IndexedMapOfShape &self, const TopoDS_Shape &s) {
|
||||
return self.Add(s);
|
||||
},
|
||||
"theKey"_a)
|
||||
.def("Contains", &TopTools_IndexedMapOfShape::Contains, "theKey"_a)
|
||||
.def("FindKey", &TopTools_IndexedMapOfShape::FindKey, "theIndex"_a,
|
||||
OCP_RETURN_COPY)
|
||||
.def("FindIndex", &TopTools_IndexedMapOfShape::FindIndex, "theKey"_a)
|
||||
.def("Extent", &TopTools_IndexedMapOfShape::Extent)
|
||||
.def("Size", &TopTools_IndexedMapOfShape::Size)
|
||||
.def("IsEmpty", &TopTools_IndexedMapOfShape::IsEmpty)
|
||||
.def("Clear",
|
||||
[](TopTools_IndexedMapOfShape &self) { self.Clear(Standard_True); })
|
||||
.def("RemoveLast", &TopTools_IndexedMapOfShape::RemoveLast)
|
||||
.def("__len__", &TopTools_IndexedMapOfShape::Size);
|
||||
|
||||
nb::class_<TopTools_IndexedDataMapOfShapeListOfShape>(
|
||||
m, "TopTools_IndexedDataMapOfShapeListOfShape")
|
||||
.def(nb::init<>())
|
||||
.def("Contains", &TopTools_IndexedDataMapOfShapeListOfShape::Contains,
|
||||
"theKey"_a)
|
||||
.def("FindKey", &TopTools_IndexedDataMapOfShapeListOfShape::FindKey,
|
||||
"theIndex"_a, OCP_RETURN_COPY)
|
||||
.def("FindIndex", &TopTools_IndexedDataMapOfShapeListOfShape::FindIndex,
|
||||
"theKey"_a)
|
||||
.def("FindFromKey",
|
||||
nb::overload_cast<const TopoDS_Shape &>(
|
||||
&TopTools_IndexedDataMapOfShapeListOfShape::FindFromKey,
|
||||
nb::const_),
|
||||
"theKey"_a, OCP_RETURN_COPY)
|
||||
.def("FindFromIndex",
|
||||
nb::overload_cast<Standard_Integer>(
|
||||
&TopTools_IndexedDataMapOfShapeListOfShape::FindFromIndex,
|
||||
nb::const_),
|
||||
"theIndex"_a, OCP_RETURN_COPY)
|
||||
.def("Extent", &TopTools_IndexedDataMapOfShapeListOfShape::Extent)
|
||||
.def("Size", &TopTools_IndexedDataMapOfShapeListOfShape::Size)
|
||||
.def("Clear",
|
||||
[](TopTools_IndexedDataMapOfShapeListOfShape &self) {
|
||||
self.Clear(Standard_True);
|
||||
})
|
||||
.def("__len__", &TopTools_IndexedDataMapOfShapeListOfShape::Size);
|
||||
}
|
||||
114
src/modules/mod_TopoDS.cpp
Normal file
114
src/modules/mod_TopoDS.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 <TopoDS.hxx>
|
||||
#include <TopoDS_CompSolid.hxx>
|
||||
#include <TopoDS_Compound.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopoDS_Iterator.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <TopoDS_Shell.hxx>
|
||||
#include <TopoDS_Solid.hxx>
|
||||
#include <TopoDS_Vertex.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
|
||||
#include <functional>
|
||||
|
||||
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_<TopoDS_Shape>(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<TopoDS_Shape>{}(s);
|
||||
});
|
||||
|
||||
#define OCP_SHAPE_SUBCLASS(Type) \
|
||||
nb::class_<Type, TopoDS_Shape>(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_<TopoDS_Iterator>(m, "TopoDS_Iterator")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<const TopoDS_Shape &, Standard_Boolean,
|
||||
Standard_Boolean>(),
|
||||
"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_<TopoDSStatics> 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
|
||||
}
|
||||
138
src/modules/mod_gp.cpp
Normal file
138
src/modules/mod_gp.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
OCP.gp — the Inc 0 subset (points, vectors, directions, axes, transforms).
|
||||
|
||||
The rest of gp (gp_Ax2/Ax3/Pln/Circ/Lin/Pnt2d/Dir2d/Quaternion, which the
|
||||
app also uses) lands with Inc 1, where the curve and surface classes that
|
||||
consume them arrive.
|
||||
*/
|
||||
|
||||
#include "../common/occt_module.h"
|
||||
#include "../common/occt_policies.h"
|
||||
|
||||
#include <gp_Ax1.hxx>
|
||||
#include <gp_Dir.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#include <gp_XYZ.hxx>
|
||||
|
||||
void register_gp(nb::module_ &root) {
|
||||
nb::module_ m = ocp_submodule(root, "gp");
|
||||
|
||||
nb::enum_<gp_TrsfForm>(m, "gp_TrsfForm", nb::is_arithmetic())
|
||||
.value("gp_Identity", gp_Identity)
|
||||
.value("gp_Rotation", gp_Rotation)
|
||||
.value("gp_Translation", gp_Translation)
|
||||
.value("gp_PntMirror", gp_PntMirror)
|
||||
.value("gp_Ax1Mirror", gp_Ax1Mirror)
|
||||
.value("gp_Ax2Mirror", gp_Ax2Mirror)
|
||||
.value("gp_Scale", gp_Scale)
|
||||
.value("gp_CompoundTrsf", gp_CompoundTrsf)
|
||||
.value("gp_Other", gp_Other)
|
||||
.export_values();
|
||||
|
||||
nb::class_<gp_XYZ>(m, "gp_XYZ")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<Standard_Real, Standard_Real, Standard_Real>(), "X"_a,
|
||||
"Y"_a, "Z"_a)
|
||||
.def("X", &gp_XYZ::X)
|
||||
.def("Y", &gp_XYZ::Y)
|
||||
.def("Z", &gp_XYZ::Z);
|
||||
|
||||
nb::class_<gp_Pnt>(m, "gp_Pnt")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<Standard_Real, Standard_Real, Standard_Real>(), "Xp"_a,
|
||||
"Yp"_a, "Zp"_a)
|
||||
.def(nb::init<const gp_XYZ &>(), "Coord"_a)
|
||||
.def("X", &gp_Pnt::X)
|
||||
.def("Y", &gp_Pnt::Y)
|
||||
.def("Z", &gp_Pnt::Z)
|
||||
.def("SetX", &gp_Pnt::SetX, "X"_a)
|
||||
.def("SetY", &gp_Pnt::SetY, "Y"_a)
|
||||
.def("SetZ", &gp_Pnt::SetZ, "Z"_a)
|
||||
.def("Coord", nb::overload_cast<>(&gp_Pnt::Coord, nb::const_),
|
||||
OCP_RETURN_COPY)
|
||||
.def("Distance", &gp_Pnt::Distance, "Other"_a)
|
||||
.def("SquareDistance", &gp_Pnt::SquareDistance, "Other"_a)
|
||||
.def("IsEqual", &gp_Pnt::IsEqual, "Other"_a, "LinearTolerance"_a)
|
||||
.def("Transform", &gp_Pnt::Transform, "T"_a)
|
||||
.def("Transformed", &gp_Pnt::Transformed, "T"_a, OCP_RETURN_COPY)
|
||||
.def("Translate",
|
||||
nb::overload_cast<const gp_Vec &>(&gp_Pnt::Translate), "V"_a)
|
||||
.def("Translated",
|
||||
nb::overload_cast<const gp_Vec &>(&gp_Pnt::Translated, nb::const_),
|
||||
"V"_a, OCP_RETURN_COPY);
|
||||
|
||||
nb::class_<gp_Vec>(m, "gp_Vec")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<Standard_Real, Standard_Real, Standard_Real>(), "Xv"_a,
|
||||
"Yv"_a, "Zv"_a)
|
||||
.def(nb::init<const gp_Dir &>(), "V"_a)
|
||||
.def(nb::init<const gp_Pnt &, const gp_Pnt &>(), "P1"_a, "P2"_a)
|
||||
.def("X", &gp_Vec::X)
|
||||
.def("Y", &gp_Vec::Y)
|
||||
.def("Z", &gp_Vec::Z)
|
||||
.def("Magnitude", &gp_Vec::Magnitude)
|
||||
.def("SquareMagnitude", &gp_Vec::SquareMagnitude)
|
||||
.def("Dot", &gp_Vec::Dot, "Other"_a)
|
||||
.def("Crossed", &gp_Vec::Crossed, "Right"_a, OCP_RETURN_COPY)
|
||||
.def("Normalize", &gp_Vec::Normalize)
|
||||
.def("Normalized", &gp_Vec::Normalized, OCP_RETURN_COPY)
|
||||
.def("Reversed", &gp_Vec::Reversed, OCP_RETURN_COPY)
|
||||
.def("Multiplied", &gp_Vec::Multiplied, "Scalar"_a, OCP_RETURN_COPY)
|
||||
.def("Angle", &gp_Vec::Angle, "Other"_a)
|
||||
.def("Transform", &gp_Vec::Transform, "T"_a)
|
||||
.def("Transformed", &gp_Vec::Transformed, "T"_a, OCP_RETURN_COPY);
|
||||
|
||||
nb::class_<gp_Dir>(m, "gp_Dir")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<Standard_Real, Standard_Real, Standard_Real>(), "Xv"_a,
|
||||
"Yv"_a, "Zv"_a)
|
||||
.def(nb::init<const gp_Vec &>(), "V"_a)
|
||||
.def("X", &gp_Dir::X)
|
||||
.def("Y", &gp_Dir::Y)
|
||||
.def("Z", &gp_Dir::Z)
|
||||
.def("Dot", &gp_Dir::Dot, "Other"_a)
|
||||
.def("Crossed", &gp_Dir::Crossed, "Right"_a, OCP_RETURN_COPY)
|
||||
.def("Angle", &gp_Dir::Angle, "Other"_a)
|
||||
.def("IsParallel", &gp_Dir::IsParallel, "Other"_a, "AngularTolerance"_a)
|
||||
.def("Reversed", &gp_Dir::Reversed, OCP_RETURN_COPY)
|
||||
.def("Reverse", &gp_Dir::Reverse)
|
||||
.def("Transform", &gp_Dir::Transform, "T"_a)
|
||||
.def("Transformed", &gp_Dir::Transformed, "T"_a, OCP_RETURN_COPY);
|
||||
|
||||
nb::class_<gp_Ax1>(m, "gp_Ax1")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<const gp_Pnt &, const gp_Dir &>(), "P"_a, "V"_a)
|
||||
.def("Location", &gp_Ax1::Location, OCP_RETURN_COPY)
|
||||
.def("Direction", &gp_Ax1::Direction, OCP_RETURN_COPY)
|
||||
.def("SetLocation", &gp_Ax1::SetLocation, "P"_a)
|
||||
.def("SetDirection", &gp_Ax1::SetDirection, "V"_a)
|
||||
.def("Reversed", &gp_Ax1::Reversed, OCP_RETURN_COPY);
|
||||
|
||||
nb::class_<gp_Trsf>(m, "gp_Trsf")
|
||||
.def(nb::init<>())
|
||||
.def("SetTranslation",
|
||||
nb::overload_cast<const gp_Vec &>(&gp_Trsf::SetTranslation),
|
||||
"V"_a)
|
||||
.def("SetTranslation",
|
||||
nb::overload_cast<const gp_Pnt &, const gp_Pnt &>(
|
||||
&gp_Trsf::SetTranslation),
|
||||
"P1"_a, "P2"_a)
|
||||
.def("SetRotation",
|
||||
nb::overload_cast<const gp_Ax1 &, Standard_Real>(
|
||||
&gp_Trsf::SetRotation),
|
||||
"A1"_a, "Ang"_a)
|
||||
.def("SetScale", &gp_Trsf::SetScale, "P"_a, "S"_a)
|
||||
.def("SetMirror", nb::overload_cast<const gp_Ax1 &>(&gp_Trsf::SetMirror),
|
||||
"A1"_a)
|
||||
.def("Form", &gp_Trsf::Form)
|
||||
.def("ScaleFactor", &gp_Trsf::ScaleFactor)
|
||||
.def("TranslationPart", &gp_Trsf::TranslationPart, OCP_RETURN_COPY)
|
||||
.def("Value", &gp_Trsf::Value, "Row"_a, "Col"_a)
|
||||
.def("Inverted", &gp_Trsf::Inverted, OCP_RETURN_COPY)
|
||||
.def("Multiplied", &gp_Trsf::Multiplied, "T"_a, OCP_RETURN_COPY)
|
||||
.def("Multiply", &gp_Trsf::Multiply, "T"_a)
|
||||
.def("PreMultiply", &gp_Trsf::PreMultiply, "T"_a)
|
||||
.def("Invert", &gp_Trsf::Invert);
|
||||
}
|
||||
Reference in New Issue
Block a user