diff --git a/cpp/modmesh/CMakeLists.txt b/cpp/modmesh/CMakeLists.txt index 1ffff0e12..a8f776785 100644 --- a/cpp/modmesh/CMakeLists.txt +++ b/cpp/modmesh/CMakeLists.txt @@ -35,6 +35,7 @@ add_subdirectory(mesh) add_subdirectory(toggle) add_subdirectory(universe) add_subdirectory(onedim) +add_subdirectory(oasis) add_subdirectory(multidim) add_subdirectory(python) add_subdirectory(spacetime) @@ -91,6 +92,7 @@ set(MODMESH_TERMINAL_FILES ${MODMESH_MULTIDIM_FILES} ${MODMESH_DEVICE_FILES} ${MODMESH_ONEDIM_FILES} + ${MODMESH_OASIS_FILES} ${MODMESH_SPACETIME_FILES} ${MODMESH_PYTHON_FILES} ${MODMESH_INOUT_FILES} diff --git a/cpp/modmesh/oasis/CMakeLists.txt b/cpp/modmesh/oasis/CMakeLists.txt new file mode 100644 index 000000000..341d01b94 --- /dev/null +++ b/cpp/modmesh/oasis/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (c) 2026, Han-Xuan Huang +# BSD-style license; see COPYING + +cmake_minimum_required(VERSION 4.0.1) + +set(MODMESH_OASIS_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/oasis_device.hpp + CACHE FILEPATH "" FORCE) + +set(MODMESH_OASIS_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/oasis_device.cpp + CACHE FILEPATH "" FORCE) + +set(MODMESH_OASIS_PYMODHEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/pymod/oasis_pymod.hpp + CACHE FILEPATH "" FORCE) + +set(MODMESH_OASIS_PYMODSOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/pymod/oasis_pymod.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pymod/wrap_oasis.cpp + CACHE FILEPATH "" FORCE) + +set(MODMESH_OASIS_FILES + ${MODMESH_OASIS_HEADERS} + ${MODMESH_OASIS_SOURCES} + ${MODMESH_OASIS_PYMODHEADERS} + ${MODMESH_OASIS_PYMODSOURCES} + CACHE FILEPATH "" FORCE) + +# vim: set ff=unix fenc=utf8 nobomb et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/oasis/oasis_device.cpp b/cpp/modmesh/oasis/oasis_device.cpp new file mode 100644 index 000000000..89a213a8f --- /dev/null +++ b/cpp/modmesh/oasis/oasis_device.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2026, Han-Xuan Huang + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +namespace modmesh +{ + +/* + * Convert value to OASIS unsigned integer bytes and append to segment array. + * Encodes payload as a base-128 variable-length sequence, emitting 7 data + * bits per byte and using the MSB as a continuation flag. + * Refer OASIS Draft 7.2.1 for more information. + */ +void OasisDevice::append_unsigned_integer(std::vector & segment, int value) +{ + int payload = value; + + if (payload == 0) + { + segment.push_back(0x00); + } + + while (payload >= 1) + { + int first_bit = payload >= 128 ? 1 : 0; + segment.push_back((first_bit << 7) | (payload % 128)); + payload /= 128; + } +} + +/* + * Convert value to OASIS signed integer bytes and append to segment array. + * 1. DIR_BIT will be 1 if value is negative, otherwise, DIRBIT will be 0. + * 2. The payload will left-shift 1 bit on value and do OR with DIR_BIT. + * 3. Encodes payload as a base-128 variable-length sequence, emitting 7 data + * bits per byte and using the MSB as a continuation flag. This operation is + * as same as OASIS unsigned interger bytes. + * Refer OASIS Draft 7.2.2 for more information. + */ +void OasisDevice::append_signed_integer(std::vector & segment, int value) +{ + const int DIR_BIT = value < 0 ? 1 : 0; + int delta_codec = abs(value) << 1 | DIR_BIT; + + int payload = delta_codec; + + if (payload == 0) + { + segment.push_back(0x02); + } + + while (payload >= 1) + { + int first_bit = payload >= 128 ? 1 : 0; + segment.push_back((first_bit << 7) | (payload % 128)); + payload /= 128; + } +} + +std::vector OasisRecordRect::to_bytes() const +{ + // RECTANGLE record should be + // '20' info-bytes [layer] [datatype] [w] [h] [x] [y] [repetition] + // Please refer OASIS Draft section 25. + std::vector segment; + + segment.push_back(0x14); + + const int S = (m_w == m_h); // Is square? (1 if yes, 0 if no) + const int W = 1; // Have width? (1 if yes, 0 if no) + const int H = (m_w != m_h); // Have height? (1 if yes, 0 if no, must be 0 if S = 1) + const int X = 1; + const int Y = 1; + const int R = 0; + const int D = 1; // Have Datatype? (1 if yes, 0 if no) + const int L = 1; // Have Layer? (1 if yes, 0 if no) + + const int INFO = (S << 7) | (W << 6) | (H << 5) | + (X << 4) | (Y << 3) | (R << 2) | (D << 1) | L; + segment.push_back(INFO); + + // Layer-num and datatype-num (0 in default). + segment.push_back(0x00); + segment.push_back(0x00); + + OasisDevice::append_unsigned_integer(segment, m_w); + OasisDevice::append_unsigned_integer(segment, m_h); + OasisDevice::append_signed_integer(segment, m_left); + OasisDevice::append_signed_integer(segment, m_lower); + + return segment; +} + +std::vector OasisRecordPoly::to_bytes() const +{ + // Polygon record should be + // '21' 00PXYRDL [layer-num] [datatype-num] [point-list] [x] [y] [rep] + // Please refer OASIS Draft section 26. + std::vector segment; + + segment.push_back(0x15); + + // Info bytes: + // - Have point-list, X and Y. + // - Have datatype and layer (Use 0) + const int P = 1; // Have Polygon-List? (1 if yes, 0 if no) + const int X = 1; + const int Y = 1; + const int R = 0; + const int D = 1; // Have Datatype? (1 if yes, 0 if no) + const int L = 1; // Have Layer? (1 if yes, 0 if no) + const int INFO = (P << 5) | (X << 4) | (Y << 3) | (R << 2) | (D << 1) | L; + segment.push_back(INFO); + + // Layer-num and datatype-num (0 in default). + segment.push_back(0x00); + segment.push_back(0x00); + + // The 1-Delta have two different type: + // - Type 0: Start with horizontal + // - Type 1: Start with vertical + if (m_vertices[0].second == m_vertices[1].second) + { + segment.push_back(0x00); + } + else if (m_vertices[0].first == m_vertices[0].first) + { + segment.push_back(0x01); + } + + // The vertex count shoud be (vertex - 1) + segment.push_back(m_vertices.size() - 1); + + // In this implementation, point-list only support 1-delta format. + // Please refer Point-list in OASIS draft 7.7. + std::vector point_list; + + for (int i = 0; i < m_vertices.size() - 1; i++) + { + std::pair curr_v = m_vertices[i]; + std::pair next_v = m_vertices[i + 1]; + + // Convert delta value to OASIS signed interegr bytes. + OasisDevice::append_signed_integer( + point_list, + (next_v.first - curr_v.first) + (next_v.second - curr_v.second)); + } + + segment.insert(segment.end(), point_list.begin(), point_list.end()); + + // X value + OasisDevice::append_signed_integer(segment, m_vertices[0].first); + + // Y value + OasisDevice::append_signed_integer(segment, m_vertices[0].second); + + return segment; +} + +std::vector OasisDevice::to_bytes() +{ + // The simple OASIS byte format should be: + // + // + // ... + // + std::vector result; + + append_magic_bytes(result); + append_start_record_bytes(result); + append_cell_and_cell_name_record_byte(result); + + for (const OasisRecordRect & record : m_rect_records) + { + append_record_bytes(result, record); + } + + for (const OasisRecordPoly & record : m_polygon_records) + { + append_record_bytes(result, record); + } + + append_end_record_byte(result); + + return result; +} + +void OasisDevice::add_poly_record(const OasisRecordPoly & record) +{ + m_polygon_records.push_back(record); +} + +void OasisDevice::add_rect_record(const OasisRecordRect & record) +{ + m_rect_records.push_back(record); +} + +void OasisDevice::append_magic_bytes(std::vector & segment) +{ + // Magic byte should be %SEMI-OASIS, which is 0x0D 0x0A. + // Please refer OASIS Draft section 6.4. + + std::string magic_byte = "%SEMI-OASIS\x0D\x0A"; + + segment.insert(segment.end(), magic_byte.begin(), magic_byte.end()); +} + +void OasisDevice::append_start_record_bytes(std::vector & segment) +{ + // START record should be + // '1' version-string unit offset-flag [ table-offsets ] + // Please refer OASIS Draft section 13. + + // The first byte should 1. + segment.push_back(0x01); + + // The version string will be [LENGTH][STRING-ASCII], we + // fixed it as "1.0". + segment.push_back(0x03); + + std::string version = "1.0"; + segment.insert(segment.end(), version.begin(), version.end()); + + // The unit we using 0.001 as default. + std::vector unit = {0x00, 0xE8, 0x07}; + segment.insert(segment.end(), unit.begin(), unit.end()); + + // The offset-flag we using 0x00 as default. + // The table-offset will store in END record if offset-flag is 0x01. + // We put 6 pairs of 0x00 to describe that we don't have any tables. + // Therefore, it insert 13 0x00 in total. + segment.insert(segment.end(), 13, 0x00); +} + +void OasisDevice::append_cell_and_cell_name_record_byte(std::vector & segment) +{ + // Append CELLNAME record, the default CELLNAME is TOP. + // TODO: This record is fixed. It can be modify by user in the future. + std::vector cellname = {0x03, 03, 0x54, 0x4F, 0x50}; + segment.insert(segment.end(), cellname.begin(), cellname.end()); + + // Append CELL record. + // TODO: This record is fixed. It can be modify by user in the future. + std::vector cell = {0x0D, 0x00}; + segment.insert(segment.end(), cell.begin(), cell.end()); +} + +void OasisDevice::append_end_record_byte(std::vector & segment) +{ + // END record should be + // '2' [table-offsets] padding validation-scheme [validation-signature] + // Please refer OASIS Draft section 14. + + // The first byte should 2. + segment.push_back(0x02); + + // Write padding. The padding should be 256 bytes. + int padding_length = 254; + segment.insert(segment.end(), padding_length, 0x00); + + // Validation-scheme: No validation. + segment.push_back(0x00); +} + +template +void OasisDevice::append_record_bytes(std::vector & bytes, const T & rec) +{ + std::vector rec_bytes = rec.to_bytes(); + bytes.insert(bytes.end(), rec_bytes.begin(), rec_bytes.end()); +} + +} // namespace modmesh + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: \ No newline at end of file diff --git a/cpp/modmesh/oasis/oasis_device.hpp b/cpp/modmesh/oasis/oasis_device.hpp new file mode 100644 index 000000000..f6ffea92b --- /dev/null +++ b/cpp/modmesh/oasis/oasis_device.hpp @@ -0,0 +1,136 @@ +#pragma once + +/* + * Copyright (c) 2026, Han-Xuan Huang + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +namespace modmesh +{ + +class OasisRecordPoly; +class OasisRecordRect; + +/** + * \class OasisDevice + * \brief OASIS device converts coordinates information to OASIS format. + * + * OasisDevice class store rectangles or polygons as OASIS format, + * the implementation based on OASIS specification, convert coordinates + * information (xy) to OASIS geometry format. The format are represented as + * byte-continuations. LSB present that the next bytes is belonging the + * group or not. + */ +class OasisDevice +{ +public: + static void append_signed_integer(std::vector & segment, int value); + static void append_unsigned_integer(std::vector & segment, int value); + + OasisDevice() = default; + OasisDevice(OasisDevice const &) = default; + OasisDevice(OasisDevice &&) = default; + OasisDevice & operator=(OasisDevice const &) = default; + OasisDevice & operator=(OasisDevice &&) = default; + ~OasisDevice() = default; + + void add_poly_record(const OasisRecordPoly & record); + void add_rect_record(const OasisRecordRect & record); + + std::vector to_bytes(); + +private: + static void append_magic_bytes(std::vector & segment); + static void append_start_record_bytes(std::vector & segment); + static void append_cell_and_cell_name_record_byte(std::vector & segment); + static void append_end_record_byte(std::vector & segment); + + template + static void append_record_bytes(std::vector & bytes, const T & record); + + std::vector m_polygon_records; + std::vector m_rect_records; +}; /* end class OasisDevice */ + +/** + * \class PolyRecord + * \brief Convert Rect information to OASIS polygon record bytes. + */ +class OasisRecordPoly +{ + +public: + explicit OasisRecordPoly(std::vector> vertices) + : m_vertices(std::move(vertices)) {}; + OasisRecordPoly() = delete; + OasisRecordPoly(OasisRecordPoly const &) = default; + OasisRecordPoly(OasisRecordPoly &&) = default; + OasisRecordPoly & operator=(OasisRecordPoly const &) = default; + OasisRecordPoly & operator=(OasisRecordPoly &&) = default; + ~OasisRecordPoly() = default; + + std::vector to_bytes() const; + +private: + std::vector> m_vertices; +}; /* end class OasisRecordPoly */ + +/** + * \class RectRecord + * \brief Convert Rect information to OASIS rectangle record bytes. + */ +class OasisRecordRect +{ +public: + OasisRecordRect(int left, int lower, int w, int h) + : m_left(left) + , m_lower(lower) + , m_w(w) + , m_h(h) + { + } + OasisRecordRect() = delete; + OasisRecordRect(OasisRecordRect const &) = default; + OasisRecordRect(OasisRecordRect &&) = default; + OasisRecordRect & operator=(OasisRecordRect const &) = default; + OasisRecordRect & operator=(OasisRecordRect &&) = default; + ~OasisRecordRect() = default; + + std::vector to_bytes() const; + +private: + int m_left, m_lower, m_w, m_h; +}; /* end class OasisRecordRect */ + +} // namespace modmesh + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: \ No newline at end of file diff --git a/cpp/modmesh/oasis/pymod/oasis_pymod.cpp b/cpp/modmesh/oasis/pymod/oasis_pymod.cpp new file mode 100644 index 000000000..4e9df2084 --- /dev/null +++ b/cpp/modmesh/oasis/pymod/oasis_pymod.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2026, Han-Xuan Huang + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include // Must be the first include. + +#include +#include + +namespace modmesh +{ + +namespace python +{ + +struct oasis_pymod_tag +{ +}; + +template <> +OneTimeInitializer & OneTimeInitializer::me() +{ + static OneTimeInitializer instance; + return instance; +} + +void initialize_oasis(pybind11::module & mod) +{ + auto initialize_impl = [](pybind11::module & mod) + { + wrap_oasis_device(mod); + }; + + OneTimeInitializer::me()(mod, initialize_impl); +} + +} /* end namespace python */ + +} /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/oasis/pymod/oasis_pymod.hpp b/cpp/modmesh/oasis/pymod/oasis_pymod.hpp new file mode 100644 index 000000000..b4e84345c --- /dev/null +++ b/cpp/modmesh/oasis/pymod/oasis_pymod.hpp @@ -0,0 +1,49 @@ +#pragma once + +/* + * Copyright (c) 2026, Han-Xuan Huang + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include // Must be the first include. + +#include +#include + +namespace modmesh +{ + +namespace python +{ + +void initialize_oasis(pybind11::module & mod); +void wrap_oasis_device(pybind11::module & mod); + +} /* end namespace python */ + +} /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/oasis/pymod/wrap_oasis.cpp b/cpp/modmesh/oasis/pymod/wrap_oasis.cpp new file mode 100644 index 000000000..ced3738a4 --- /dev/null +++ b/cpp/modmesh/oasis/pymod/wrap_oasis.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2026, Han-Xuan Huang + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include // Must be the first include. +#include +#include + +#include + +namespace modmesh +{ + +namespace python +{ + +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapOasisDevice + : public WrapBase> +{ + +public: + + using base_type = WrapBase>; + using wrapper_type = typename base_type::wrapper_type; + using wrapped_type = typename base_type::wrapped_type; + + friend base_type; + +protected: + + WrapOasisDevice(pybind11::module & mod, const char * pyname, const char * clsdoc) + : base_type(mod, pyname, clsdoc) + { + namespace py = pybind11; + + (*this) + .def(py::init()) + .def("to_bytes", &wrapped_type::to_bytes) + .def("add_rect_record", &wrapped_type::add_rect_record, py::arg("rect_record")) + .def("add_poly_record", &wrapped_type::add_poly_record, py::arg("polygon_record")); + } +}; /* end class WrapOasisDevice */ + +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapOasisRecordPoly + : public WrapBase> +{ + +public: + + using base_type = WrapBase>; + using wrapper_type = typename base_type::wrapper_type; + using wrapped_type = typename base_type::wrapped_type; + + friend base_type; + +protected: + + WrapOasisRecordPoly(pybind11::module & mod, const char * pyname, const char * clsdoc) + : base_type(mod, pyname, clsdoc) + { + namespace py = pybind11; + + (*this) + .def(py::init>>(), py::arg("coords")) + .def("to_bytes", &wrapped_type::to_bytes); + } +}; /* end class WrapOasisRecordPoly */ + +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapOasisRecordRect + : public WrapBase> +{ + +public: + + using base_type = WrapBase>; + using wrapper_type = typename base_type::wrapper_type; + using wrapped_type = typename base_type::wrapped_type; + + friend base_type; + +protected: + + WrapOasisRecordRect(pybind11::module & mod, const char * pyname, const char * clsdoc) + : base_type(mod, pyname, clsdoc) + { + namespace py = pybind11; + + (*this) + .def(py::init(), py::arg("lower"), py::arg("left"), py::arg("w"), py::arg("h")) + .def("to_bytes", &wrapped_type::to_bytes); + } +}; /* end class WrapOasisRecordRect */ + +void wrap_oasis_device(pybind11::module & mod) +{ + WrapOasisDevice::commit(mod, "OasisDevice", "OASIS bytes device"); + WrapOasisRecordPoly::commit(mod, "OasisRecordPoly", "OASIS polygon record"); + WrapOasisRecordRect::commit(mod, "OasisRecordRect", "OASIS rectangle record"); +} + +} /* end namespace python */ + +} /* end namespace modmesh */ + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/python/module.cpp b/cpp/modmesh/python/module.cpp index 8ab6669a1..5d0b40d04 100644 --- a/cpp/modmesh/python/module.cpp +++ b/cpp/modmesh/python/module.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #ifdef USE_PYTEST_HELPER_BINDING #include @@ -63,6 +64,7 @@ void initialize(pybind11::module_ mod) pybind11::module_ onedim_mod = mod.def_submodule("onedim", "onedim"); initialize_onedim(onedim_mod); initialize_transform(mod); + initialize_oasis(mod); pybind11::module_ testhelper_mod = mod.def_submodule("testhelper", "testhelper"); #ifdef USE_PYTEST_HELPER_BINDING diff --git a/modmesh/core.py b/modmesh/core.py index 7ac458fc8..59f1c232c 100644 --- a/modmesh/core.py +++ b/modmesh/core.py @@ -173,6 +173,13 @@ 'TrapezoidalDecomposerFp64', ] +# OASIS directory symbols +list_of_oasis = [ + 'OasisDevice', + 'OasisRecordRect', + 'OasisRecordPoly', +] + __all__ = ( # noqa: F822 list_of_buffer + list_of_inout + @@ -184,7 +191,8 @@ list_of_toggle + list_of_transform + list_of_linalg + - list_of_universe + list_of_universe + + list_of_oasis ) @@ -212,6 +220,7 @@ def _load(symbol_list): _load(list_of_transform) _load(list_of_linalg) _load(list_of_universe) +_load(list_of_oasis) # Walk through the thirdparty folder and register all library # into a dictionary. diff --git a/tests/test_oasis_device.py b/tests/test_oasis_device.py new file mode 100644 index 000000000..03f1d82f5 --- /dev/null +++ b/tests/test_oasis_device.py @@ -0,0 +1,136 @@ +# Copyright (c) 2026, Han-Xuan Huang +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +import unittest +import modmesh + + +class OasisRecordRectTC(unittest.TestCase): + # Please refer the comment of modmesh::OasisRecordRect in oasis_device.cpp + def test_to_byte(self): + rec = modmesh.OasisRecordRect(70, 800, 180, 40) + + expected_record_bytes = '\x14\x7B\x00\x00\xB4\x01\x28\x8C\x01\xC0\x0C' + record_bytes = rec.to_bytes() + + self.assertEqual(record_bytes, list(map(ord, expected_record_bytes))) + + +class OasisRecordPolyTC(unittest.TestCase): + # Please refer the comment of modmesh::OasisRecordPoly in oasis_device.cpp + def test_type_1_to_byte(self): + rec = modmesh.OasisRecordPoly([ + [410, 720], [410, 920], [70, 920], + [70, 880], [370, 880], [370, 760], [70, 760], [70, 720]]) + + expected_record_bytes = '\x15\x3B\x00\x00\x01\x07' \ + '\x90\x03\xA9\x05\x51\xD8' \ + '\x04\xF1\x01\xD9\x04\x51' \ + '\xB4\x06\xA0\x0B' + record_bytes = rec.to_bytes() + + self.assertEqual(record_bytes, list(map(ord, expected_record_bytes))) + + def test_type_0_to_byte(self): + rec = modmesh.OasisRecordPoly([ + [70, 720], [410, 720], [410, 920], [70, 920], + [70, 880], [370, 880], [370, 760], [70, 760]]) + + expected_record_bytes = '\x15\x3B\x00\x00\x00\x07' \ + '\xA8\x05\x90\x03\xA9\x05' \ + '\x51\xD8\x04\xF1\x01\xD9' \ + '\x04\x8C\x01\xA0\x0B' + record_bytes = rec.to_bytes() + + self.assertEqual(record_bytes, list(map(ord, expected_record_bytes))) + + +# For OASIS format, refer modmesh::OasisDevice comment in oasis_device.cpp +class OasisDeviceTC(unittest.TestCase): + def oasis_bytes(self, records=None): + magic_bytes = '%SEMI-OASIS\x0D\x0A' + start = "\x01\x031.0\x00\xE8\x07" + '\x00' * 13 + cell = "\x03\x03\x54\x4F\x50\x0D\x00" + end = '\x02' + '\x00' * 254 + '\x00' + + if records is None: + return list(map(ord, magic_bytes + start + cell + end)) + else: + return list(map(ord, magic_bytes + start + cell + records + end)) + + def test_empty_oasis(self): + device = modmesh.OasisDevice() + oasis_bytes = device.to_bytes() + + self.assertEqual(oasis_bytes, self.oasis_bytes()) + + def test_rect_oasis(self): + device = modmesh.OasisDevice() + rec = modmesh.OasisRecordRect(70, 800, 180, 40) + + device.add_rect_record(rec) + + oasis_bytes = device.to_bytes() + rec_record_bytes = '\x14\x7B\x00\x00\xB4\x01\x28\x8C\x01\xC0\x0C' + self.assertEqual(oasis_bytes, self.oasis_bytes(rec_record_bytes)) + + def test_poly_oasis(self): + device = modmesh.OasisDevice() + rec = modmesh.OasisRecordPoly([ + [70, 720], [410, 720], [410, 920], [70, 920], + [70, 880], [370, 880], [370, 760], [70, 760]]) + + device.add_poly_record(rec) + + oasis_bytes = device.to_bytes() + poly_record_bytes = '\x15\x3B\x00\x00\x00\x07' \ + '\xA8\x05\x90\x03\xA9\x05' \ + '\x51\xD8\x04\xF1\x01\xD9' \ + '\x04\x8C\x01\xA0\x0B' + self.assertEqual(oasis_bytes, self.oasis_bytes(poly_record_bytes)) + + def test_mix_geometry_record_oasis(self): + device = modmesh.OasisDevice() + rec_poly = modmesh.OasisRecordPoly([ + [70, 720], [410, 720], [410, 920], [70, 920], + [70, 880], [370, 880], [370, 760], [70, 760]]) + rec_rect = modmesh.OasisRecordRect(70, 800, 180, 40) + + device.add_poly_record(rec_poly) + device.add_rect_record(rec_rect) + + oasis_bytes = device.to_bytes() + rec_record_bytes = '\x14\x7B\x00\x00\xB4\x01\x28\x8C\x01\xC0\x0C' + poly_record_bytes = '\x15\x3B\x00\x00\x00\x07' \ + '\xA8\x05\x90\x03\xA9\x05' \ + '\x51\xD8\x04\xF1\x01\xD9' \ + '\x04\x8C\x01\xA0\x0B' + mix_record_bytes = rec_record_bytes + poly_record_bytes + self.assertEqual(oasis_bytes, self.oasis_bytes(mix_record_bytes)) + + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: