diff --git a/OCP_specific.inc b/OCP_specific.inc index c0e3c1d49..8fd597715 100644 --- a/OCP_specific.inc +++ b/OCP_specific.inc @@ -2,6 +2,7 @@ #include #include #include +#include namespace py = pybind11; @@ -46,6 +47,13 @@ inline void copy_if_copy_constructible(T& t1, T& t2){ }; +template inline opencascade::handle cast_pyobject(const py::object &arg){ + if(py::isinstance(arg, py::type::of())) + return opencascade::handle(py::cast(arg)); + else + throw py::value_error(fmt::format("Cannot convert the argument to the required type {}", typeid(T).name())); +}; + template typename Deleter = std::default_delete> struct shared_ptr : public std::shared_ptr{ explicit shared_ptr(T* t = nullptr) : std::shared_ptr(t, Deleter()) {}; diff --git a/environment.devenv.yml b/environment.devenv.yml index b1f3219f4..154576d57 100644 --- a/environment.devenv.yml +++ b/environment.devenv.yml @@ -26,6 +26,7 @@ dependencies: {% else %} - cxx-compiler - tbb-devel # [win] + - fmt - pip: - git+https://github.com/CadQuery/pybind11-stubgen.git {% endif %} diff --git a/ocp.toml b/ocp.toml index 4faf32330..ce5a38533 100644 --- a/ocp.toml +++ b/ocp.toml @@ -30,7 +30,6 @@ modules = [ "Quantity", "Storage", "FSD", -"MMgt", "TCollection", "TColStd", "Message", @@ -269,6 +268,7 @@ modules = [ "RWStepGeom", "RWStepRepr", "RWStepShape", +"RWMesh", "StepBasic", "StepGeom", "StepRepr", @@ -424,7 +424,7 @@ class Adaptor3d_Surface; [Attributes] - __version__ = "7.9.3.0" + __version__ = "7.9.3.1" [Modules] @@ -493,6 +493,20 @@ void register_template_NCollection_Vector(py::object &m, const char *name){ [Modules.TCollection] exclude_methods = ["TCollection_ExtendedString::ToUTF8CString"] + include_body_post = """py::implicitly_convertible(); +py::implicitly_convertible();""" + + [[Modules.TCollection.Classes.TCollection_HAsciiString.additional_constructors]] + + body = "[](const std::string &s){ return TCollection_HAsciiString(s.c_str()); }" + help = "std::strings based constructor" + arguments = ["aString"] + + [[Modules.TCollection.Classes.TCollection_AsciiString.additional_constructors]] + + body = "[](const std::string &s){ return TCollection_AsciiString(s.c_str()); }" + help = "std::strings based constructor" + arguments = ["aString"] [Modules.TColStd] @@ -1090,6 +1104,12 @@ struct __GLXFBConfigRec {};""" exclude_classes = ["StepData_EnumTool"] + [Modules.StepData.Classes.StepData_SelectType.additional_methods.SetValue] + + body = "[](StepData_SelectType &self , py::object& ent){self.SetValue(cast_pyobject(ent));}" + help = "SetValue supporting python objects" + arguments = ["ent"] + [Modules.LDOM] exclude_classes = ["LDOMString","LDOM_MemManager","LDOM_BasicText"] @@ -1279,6 +1299,18 @@ using rapidjson::BasicOStreamWrapper;""" "Interface_ValueInterpret.hxx" = "#include " + [Modules.Interface.Classes.Interface_InterfaceModel.additional_methods.AddEntity] + + body = "[](Interface_InterfaceModel &self , py::object& ent){self.AddEntity(cast_pyobject(ent));}" + help = "AddEntity supporting python objects" + arguments = ["anent"] + + [Modules.Interface.Classes.Interface_InterfaceModel.additional_methods.AddWithRefs] + + body = "[](Interface_InterfaceModel &self , py::object& ent){self.AddWithRefs(cast_pyobject(ent));}" + help = "AddWithRefs supporting python objects" + arguments = ["anent"] + [Modules.IVtkVTK] include_header_pre = """#include @@ -1495,3 +1527,7 @@ using rapidjson::BasicOStreamWrapper;""" [Modules.StepAP214] include_header_pre = "#include " + + [Modules.RWMesh] + + include_header_pre = "#include " diff --git a/pystreambuf.h b/pystreambuf.h index 23480a1b7..99905fd6d 100644 --- a/pystreambuf.h +++ b/pystreambuf.h @@ -1,5 +1,6 @@ /* Based on https://gist.github.com/asford/544323a5da7dddad2c9174490eb5ed06 +but was heavily modified. Original license text --------------------- @@ -58,529 +59,79 @@ derivative works thereof, in binary and source code form. #include -#include #include +#include namespace py = pybind11; -/// A stream buffer getting data from and putting data into a Python file object -/** The aims are as follow: - - Given a C++ function acting on a standard stream, e.g. - - \code - void read_inputs(std::istream& input) { - ... - input >> something >> something_else; - } - \endcode - - and given a piece of Python code which creates a file-like object, - to be able to pass this file object to that C++ function, e.g. - - \code - import gzip - gzip_file_obj = gzip.GzipFile(...) - read_inputs(gzip_file_obj) - \endcode - - and have the standard stream pull data from and put data into the Python - file object. - - - When Python \c read_inputs() returns, the Python object is able to - continue reading or writing where the C++ code left off. - - - Operations in C++ on mere files should be competitively fast compared - to the direct use of \c std::fstream. - - - \b Motivation - - - the standard Python library offer of file-like objects (files, - compressed files and archives, network, ...) is far superior to the - offer of streams in the C++ standard library and Boost C++ libraries. - - - i/o code involves a fair amount of text processing which is more - efficiently prototyped in Python but then one may need to rewrite - a time-critical part in C++, in as seamless a manner as possible. - - \b Usage - - This is 2-step: - - - a trivial wrapper function - - \code - using boost_adaptbx::python::streambuf; - void read_inputs_wrapper(streambuf& input) - { - streambuf::istream is(input); - read_inputs(is); - } - - def("read_inputs", read_inputs_wrapper); - \endcode - - which has to be written every time one wants a Python binding for - such a C++ function. - - - the Python side - - \code - from boost.python import streambuf - read_inputs(streambuf(python_file_obj=obj, buffer_size=1024)) - \endcode - - \c buffer_size is optional. See also: \c default_buffer_size - - Note: references are to the C++ standard (the numbers between parentheses - at the end of references are margin markers). -*/ - -namespace pystream{ - -class streambuf : public std::basic_streambuf -{ - private: - typedef std::basic_streambuf base_t; - - public: - /* The syntax - using base_t::char_type; - would be nicer but Visual Studio C++ 8 chokes on it - */ - typedef base_t::char_type char_type; - typedef base_t::int_type int_type; - typedef base_t::pos_type pos_type; - typedef base_t::off_type off_type; - typedef base_t::traits_type traits_type; - - /// The default size of the read and write buffer. - /** They are respectively used to buffer data read from and data written to - the Python file object. It can be modified from Python. - */ - static inline std::size_t default_buffer_size = 1024; - - /// Construct from a Python file object - /** if buffer_size is 0 the current default_buffer_size is used. - */ - streambuf( - py::object& python_file_obj, - std::size_t buffer_size_=0) - : - py_read (getattr(python_file_obj, "read", py::none())), - py_write (getattr(python_file_obj, "write", py::none())), - py_seek (getattr(python_file_obj, "seek", py::none())), - py_tell (getattr(python_file_obj, "tell", py::none())), - buffer_size(buffer_size_ != 0 ? buffer_size_ : default_buffer_size), - write_buffer(0), - pos_of_read_buffer_end_in_py_file(0), - pos_of_write_buffer_end_in_py_file(buffer_size), - farthest_pptr(0) - { - assert(buffer_size != 0); - /* Some Python file objects (e.g. sys.stdout and sys.stdin) - have non-functional seek and tell. If so, assign None to - py_tell and py_seek. - */ - if (!py_tell.is_none()) { - try { - py_tell(); - } - catch (py::error_already_set& err) { - py_tell = py::none(); - py_seek = py::none(); - err.restore(); - PyErr_Clear(); - } - } - - if (!py_write.is_none()) { - // C-like string to make debugging easier - write_buffer = new char[buffer_size + 1]; - write_buffer[buffer_size] = '\0'; - setp(write_buffer, write_buffer + buffer_size); // 27.5.2.4.5 (5) - farthest_pptr = pptr(); - } - else { - // The first attempt at output will result in a call to overflow - setp(0, 0); - } - - if (!py_tell.is_none()){ - off_type py_pos = py_tell().cast(); - pos_of_read_buffer_end_in_py_file = py_pos; - pos_of_write_buffer_end_in_py_file = py_pos; - } - } - - /// Mundane destructor freeing the allocated resources - virtual ~streambuf() { - if (write_buffer) delete[] write_buffer; - } - - /// C.f. C++ standard section 27.5.2.4.3 - /** It is essential to override this virtual function for the stream - member function readsome to work correctly (c.f. 27.6.1.3, alinea 30) - */ - virtual std::streamsize showmanyc() { - int_type const failure = traits_type::eof(); - int_type status = underflow(); - if (status == failure) return -1; - return egptr() - gptr(); - } - - /// C.f. C++ standard section 27.5.2.4.3 - virtual int_type underflow() { - int_type const failure = traits_type::eof(); - if (py_read.is_none()) { - throw std::invalid_argument( - "That Python file object has no 'read' attribute"); - } - read_buffer = py_read(buffer_size); - char *read_buffer_data; - py::ssize_t py_n_read; - if (PYBIND11_BYTES_AS_STRING_AND_SIZE(read_buffer.ptr(), - &read_buffer_data, &py_n_read) == -1) { - setg(0, 0, 0); - throw std::invalid_argument( - "The method 'read' of the Python file object " - "did not return a string."); - } - off_type n_read = (off_type)py_n_read; - pos_of_read_buffer_end_in_py_file += n_read; - setg(read_buffer_data, read_buffer_data, read_buffer_data + n_read); - // ^^^27.5.2.3.1 (4) - if (n_read == 0) return failure; - return traits_type::to_int_type(read_buffer_data[0]); - } - - /// C.f. C++ standard section 27.5.2.4.5 - virtual int_type overflow(int_type c=traits_type::eof()) { - if (py_write.is_none()) { - throw std::invalid_argument( - "That Python file object has no 'write' attribute"); - } - farthest_pptr = (std::max)(farthest_pptr, pptr()); - off_type n_written = (off_type)(farthest_pptr - pbase()); - py::bytes chunk(pbase(), n_written); - py_write(chunk); - if (!traits_type::eq_int_type(c, traits_type::eof())) { - char cs = traits_type::to_char_type(c); - py_write(py::bytes(&cs, 1)); - n_written++; - } - if (n_written) { - pos_of_write_buffer_end_in_py_file += n_written; - setp(pbase(), epptr()); - // ^^^ 27.5.2.4.5 (5) - farthest_pptr = pptr(); - } - return traits_type::eq_int_type( - c, traits_type::eof()) ? traits_type::not_eof(c) : c; - } - - /// Update the python file to reflect the state of this stream buffer - /** Empty the write buffer into the Python file object and set the seek - position of the latter accordingly (C++ standard section 27.5.2.4.2). - If there is no write buffer or it is empty, but there is a non-empty - read buffer, set the Python file object seek position to the - seek position in that read buffer. - */ - virtual int sync() { - int result = 0; - farthest_pptr = (std::max)(farthest_pptr, pptr()); - if (farthest_pptr && farthest_pptr > pbase()) { - off_type delta = pptr() - farthest_pptr; - int_type status = overflow(); - if (traits_type::eq_int_type(status, traits_type::eof())) result = -1; - if (!py_seek.is_none()) py_seek(delta, 1); - } - else if (gptr() && gptr() < egptr()) { - if (!py_seek.is_none()) py_seek(gptr() - egptr(), 1); - } - return result; - } - - /// C.f. C++ standard section 27.5.2.4.2 - /** This implementation is optimised to look whether the position is within - the buffers, so as to avoid calling Python seek or tell. It is - important for many applications that the overhead of calling into Python - is avoided as much as possible (e.g. parsers which may do a lot of - backtracking) - */ - virtual - pos_type seekoff(off_type off, std::ios_base::seekdir way, - std::ios_base::openmode which= std::ios_base::in - | std::ios_base::out) - { - /* In practice, "which" is either std::ios_base::in or out - since we end up here because either seekp or seekg was called - on the stream using this buffer. That simplifies the code - in a few places. - */ - int const failure = off_type(-1); - - if (py_seek.is_none()) { - throw std::invalid_argument( - "That Python file object has no 'seek' attribute"); - } - - // we need the read buffer to contain something! - if (which == std::ios_base::in && !gptr()) { - if (traits_type::eq_int_type(underflow(), traits_type::eof())) { - return failure; - } - } - - // compute the whence parameter for Python seek - int whence; - switch (way) { - case std::ios_base::beg: - whence = 0; - break; - case std::ios_base::cur: - whence = 1; - break; - case std::ios_base::end: - whence = 2; - break; - default: - return failure; - } - - // Let's have a go - off_type result; - if (!seekoff_without_calling_python(off, way, which, result)) { - // we need to call Python - if (which == std::ios_base::out) overflow(); - if (way == std::ios_base::cur) { - if (which == std::ios_base::in) off -= egptr() - gptr(); - else if (which == std::ios_base::out) off += pptr() - pbase(); - } - py_seek(off, whence); - result = off_type(py_tell().cast()); - if (which == std::ios_base::in) underflow(); - } - return result; - } - - /// C.f. C++ standard section 27.5.2.4.2 - virtual - pos_type seekpos(pos_type sp, - std::ios_base::openmode which= std::ios_base::in - | std::ios_base::out) - { - return streambuf::seekoff(sp, std::ios_base::beg, which); - } - - private: - py::object py_read, py_write, py_seek, py_tell; - - std::size_t buffer_size; - - /* This is actually a Python bytes object and the actual read buffer is - its internal data, i.e. an array of characters. - */ - py::bytes read_buffer; - - /* A mere array of char's allocated on the heap at construction time and - de-allocated only at destruction time. - */ - char *write_buffer; - - off_type pos_of_read_buffer_end_in_py_file, - pos_of_write_buffer_end_in_py_file; - - // the farthest place the buffer has been written into - char *farthest_pptr; - - - bool seekoff_without_calling_python( - off_type off, - std::ios_base::seekdir way, - std::ios_base::openmode which, - off_type & result) - { - // Buffer range and current position - off_type buf_begin, buf_end, buf_cur, upper_bound; - off_type pos_of_buffer_end_in_py_file; - if (which == std::ios_base::in) { - pos_of_buffer_end_in_py_file = pos_of_read_buffer_end_in_py_file; - buf_begin = reinterpret_cast(eback()); - buf_cur = reinterpret_cast(gptr()); - buf_end = reinterpret_cast(egptr()); - upper_bound = buf_end; - } - else if (which == std::ios_base::out) { - pos_of_buffer_end_in_py_file = pos_of_write_buffer_end_in_py_file; - buf_begin = reinterpret_cast(pbase()); - buf_cur = reinterpret_cast(pptr()); - buf_end = reinterpret_cast(epptr()); - farthest_pptr = (std::max)(farthest_pptr, pptr()); - upper_bound = reinterpret_cast(farthest_pptr) + 1; - } - else { - std::runtime_error( - "Control flow passes through branch that should be unreachable."); - } - - // Sought position in "buffer coordinate" - off_type buf_sought; - if (way == std::ios_base::cur) { - buf_sought = buf_cur + off; - } - else if (way == std::ios_base::beg) { - buf_sought = buf_end + (off - pos_of_buffer_end_in_py_file); - } - else if (way == std::ios_base::end) { - return false; - } - else { - std::runtime_error( - "Control flow passes through branch that should be unreachable."); - } - - // if the sought position is not in the buffer, give up - if (buf_sought < buf_begin || buf_sought >= upper_bound) return false; - - // we are in wonderland - if (which == std::ios_base::in) gbump(buf_sought - buf_cur); - else if (which == std::ios_base::out) pbump(buf_sought - buf_cur); - - result = pos_of_buffer_end_in_py_file + (buf_sought - buf_end); - return true; - } - - public: - - class istream : public std::istream - { - public: - istream(streambuf& buf) : std::istream(&buf) - { - exceptions(std::ios_base::badbit); - } +namespace pybind11::detail { + template <> struct type_caster { - ~istream() { if (this->good()) this->sync(); } - }; + bool load(handle src, bool) { - class ostream : public std::ostream - { - public: - ostream(streambuf& buf) : std::ostream(&buf) - { - exceptions(std::ios_base::badbit); - } + auto read_method = getattr(src, "read", py::none()); - ~ostream() { if (this->good()) this->flush(); } - }; -}; - - -struct streambuf_capsule -{ - streambuf python_streambuf; - - streambuf_capsule( - py::object& python_file_obj, - std::size_t buffer_size=0) - : - python_streambuf(python_file_obj, buffer_size) - {} -}; - -struct ostream : private streambuf_capsule, streambuf::ostream -{ - ostream( - py::object& python_file_obj, - std::size_t buffer_size=0) - : - streambuf_capsule(python_file_obj, buffer_size), - streambuf::ostream(python_streambuf) - {} - - ~ostream() - { - if (this->good()){ - this->flush(); - } - } -}; - -struct istream : private streambuf_capsule, streambuf::istream -{ - istream( - py::object& python_file_obj, - std::size_t buffer_size=0) - : - streambuf_capsule(python_file_obj, buffer_size), - streambuf::istream(python_streambuf) - {} - - ~istream() - { - if (this->good()) { - this->sync(); - } - } -}; - -}; - -namespace pybind11 { namespace detail { - template <> struct type_caster { - public: - bool load(handle src, bool) { - if (getattr(src, "read", py::none()).is_none()){ + if (read_method.is_none()){ return false; } - obj = py::reinterpret_borrow(src); - value = std::unique_ptr(new pystream::istream(obj, 0)); + value = std::make_unique(py::reinterpret_borrow(read_method())); return true; } - protected: - py::object obj; - std::unique_ptr value; - - public: - static constexpr auto name = _("io.BytesIO"); static handle cast(std::istream &src, return_value_policy policy, handle parent) { return none().release(); } + + static constexpr auto name = const_name("io.BytesIO"); operator std::istream*() { return value.get(); } operator std::istream&() { return *value; } template using cast_op_type = pybind11::detail::cast_op_type<_T>; + + std::unique_ptr value; + }; template <> struct type_caster { - public: + + // std::ostringstream subclass writing back to python upon destruction + struct pyostringstream : std::ostringstream { + + py::object write_method; + + pyostringstream(py::object write_method_) : std::ostringstream{}, write_method{write_method_} {}; + + // Write back to the python object + ~pyostringstream(){ this->write_method(py::bytes(this->str()));} + + }; + bool load(handle src, bool) { - if (getattr(src, "write", py::none()).is_none()){ + + auto write_method = getattr(src, "write", py::none()); + + if (write_method.is_none()){ return false; } - obj = py::reinterpret_borrow(src); - value = std::unique_ptr(new pystream::ostream(obj, 0)); + value = std::make_unique(write_method); return true; } - protected: - py::object obj; - std::unique_ptr value; - - public: - static constexpr auto name = _("io.BytesIO"); static handle cast(std::ostream &src, return_value_policy policy, handle parent) { return none().release(); } + + static constexpr auto name = const_name("io.BytesIO"); operator std::ostream*() { return value.get(); } operator std::ostream&() { return *value; } template using cast_op_type = pybind11::detail::cast_op_type<_T>; + + std::unique_ptr value; + }; -}} // namespace pybind11::detail +} // namespace pybind11::detail diff --git a/templates/CMakeLists.j2 b/templates/CMakeLists.j2 index 438b080b8..296ed8365 100755 --- a/templates/CMakeLists.j2 +++ b/templates/CMakeLists.j2 @@ -13,6 +13,7 @@ message( STATUS "Python lib: ${Python_LIBRARIES}" ) SET(PYTHON_SP_DIR "site-packages" CACHE PATH "Python site-packages directory (for installing)") +find_package( fmt REQUIRED ) find_package( pybind11 REQUIRED ) find_package( VTK REQUIRED COMPONENTS @@ -34,7 +35,7 @@ add_library( {{ name }} MODULE ${CPP_FILES} ) target_include_directories( {{ name }} PRIVATE ${OpenCASCADE_INCLUDE_DIR} ) target_link_libraries( {{ name }} PRIVATE ${OpenCASCADE_LIBRARIES} ) -target_link_libraries( {{ name }} PRIVATE pybind11::pybind11 VTK::WrappingPythonCore VTK::RenderingCore VTK::CommonDataModel VTK::CommonExecutionModel) +target_link_libraries( {{ name }} PRIVATE pybind11::pybind11 VTK::WrappingPythonCore VTK::RenderingCore VTK::CommonDataModel VTK::CommonExecutionModel fmt::fmt) set_target_properties( {{ name }} PROPERTIES