diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c131ef2..233e672 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,9 @@ jobs: exodusii==2022.10.* \ fmt=11.* \ yaml-cpp==0.8.0 \ + pybind11 \ + pytest \ + flake8 \ lcov - name: Configure @@ -47,6 +50,7 @@ jobs: cmake -S . -B ${{ github.workspace }}/build \ -DGCOV_PATH=`which x86_64-conda-linux-gnu-gcov` \ -DEXODUSIICPP_LIBRARY_TYPE=SHARED \ + -DEXODUSIICPP_WITH_PYTHON=ON \ -DEXODUSIICPP_BUILD_TESTS=YES \ -DEXODUSIICPP_CODE_COVERAGE=YES diff --git a/CMakeLists.txt b/CMakeLists.txt index b0151e4..9db7f06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.26) project(exodusIIcpp VERSION 3.0.0 @@ -24,6 +24,7 @@ set_property(CACHE EXODUSIICPP_LIBRARY_TYPE PROPERTY STRINGS ${LibraryTypeValues option(EXODUSIICPP_BUILD_TESTS "Build tests" NO) option(EXODUSIICPP_BUILD_TOOLS "Build tools" YES) option(EXODUSIICPP_INSTALL "Install the library" ON) +option(EXODUSIICPP_WITH_PYTHON "Build python wrapper" NO) mark_as_advanced(FORCE EXODUSIICPP_INSTALL) find_package(fmt 11 REQUIRED) @@ -59,6 +60,10 @@ if (EXODUSIICPP_BUILD_TOOLS) add_subdirectory(tools) endif() +if (EXODUSIICPP_WITH_PYTHON) + add_subdirectory(python) +endif() + # Tests if (EXODUSIICPP_BUILD_TESTS) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 0000000..da5c9f9 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(Python3 REQUIRED COMPONENTS Interpreter Development) + +configure_file(pyproject.toml.in pyproject.toml) +install(CODE "execute_process(COMMAND ${Python3_EXECUTABLE} -m pip install ${CMAKE_CURRENT_BINARY_DIR})") + +add_subdirectory(src) + +if (EXODUSIICPP_BUILD_TESTS) + find_program(PYTEST "pytest" REQUIRED) + enable_testing() + add_test( + NAME python-tests + COMMAND ${PYTEST} ${CMAKE_CURRENT_SOURCE_DIR} + ) +endif() diff --git a/python/pyproject.toml.in b/python/pyproject.toml.in new file mode 100644 index 0000000..e490938 --- /dev/null +++ b/python/pyproject.toml.in @@ -0,0 +1,10 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "exodusIIcpp" +version = "${PROJECT_VERSION}" + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/python/src/CMakeLists.txt b/python/src/CMakeLists.txt new file mode 100644 index 0000000..8042bf2 --- /dev/null +++ b/python/src/CMakeLists.txt @@ -0,0 +1,38 @@ +project(pyexodusIIcpp LANGUAGES C CXX) + +find_package(pybind11 2.9 REQUIRED) + +pybind11_add_module(pyexodusIIcpp exodusIIcpp.cpp) + +set_target_properties(pyexodusIIcpp PROPERTIES OUTPUT_NAME exodusIIcpp) + +target_include_directories( + pyexodusIIcpp + PRIVATE + ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR} +) + +target_link_libraries( + pyexodusIIcpp + PUBLIC + exodusIIcpp +) + +configure_file(version.h.in version.h) + +set(PYTHON_SITE lib/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages) + +install( + TARGETS pyexodusIIcpp + COMPONENT python + LIBRARY DESTINATION ${PYTHON_SITE}/exodusIIcpp +) + +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/exodusIIcpp/__init__.py + DESTINATION ${PYTHON_SITE}/exodusIIcpp + COMPONENT python +) diff --git a/python/src/exodusIIcpp.cpp b/python/src/exodusIIcpp.cpp new file mode 100644 index 0000000..e3cafb9 --- /dev/null +++ b/python/src/exodusIIcpp.cpp @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025 David Andrs +// SPDX-License-Identifier: MIT + +#include +#include +#include "exodusIIcpp/exodusIIcpp.h" +#include "version.h" + +using namespace exodusIIcpp; + +namespace py = pybind11; + +PYBIND11_MODULE(exodusIIcpp, m) +{ + m.doc() = "pybind11 plugin for exodusIIcpp"; + py::setattr(m, "version", py::str(EXODUSIICPP_VERSION)); + + py::class_(m, "File") + .def(py::init()) + .def("open", &File::open) + .def("create", &File::create) + .def("is_opened", &File::is_opened) + .def("init", static_cast(&File::init)) + .def("init", + static_cast(&File::init)) + // + .def("get_title", &File::get_title) + .def("get_dim", &File::get_dim) + .def("get_num_nodes", &File::get_num_nodes) + .def("get_num_elements", &File::get_num_elements) + .def("get_num_element_blocks", &File::get_num_element_blocks) + .def("get_num_node_sets", &File::get_num_node_sets) + .def("get_num_side_sets", &File::get_num_side_sets) + .def("get_x_coords", &File::get_x_coords) + .def("get_y_coords", &File::get_y_coords) + .def("get_z_coords", &File::get_z_coords) + .def("get_coord_names", &File::get_coord_names) + .def("get_element_block", &File::get_element_block) + .def("get_side_sets", &File::get_side_sets) + .def("get_side_set_node_list", &File::get_side_set_node_list) + .def("get_node_sets", &File::get_node_sets) + .def("get_num_times", &File::get_num_times) + .def("get_times", &File::get_times) + .def("get_nodal_variable_names", &File::get_nodal_variable_names) + .def("get_elemental_variable_names", &File::get_elemental_variable_names) + .def("get_global_variable_names", &File::get_global_variable_names) + .def("get_nodal_variable_values", &File::get_nodal_variable_values) + .def("get_elemental_variable_values", &File::get_elemental_variable_values) + .def("get_global_variable_values", + static_cast (File::*)(int) const>( + &File::get_global_variable_values)) + // read + .def("read", &File::read) + .def("read_coords", &File::read_coords) + .def("read_coord_names", &File::read_coord_names) + .def("read_elem_map", &File::read_elem_map) + .def("read_blocks", &File::read_blocks) + .def("read_block_names", &File::read_block_names) + .def("read_node_sets", &File::read_node_sets) + .def("read_node_set_names", &File::read_node_set_names) + .def("read_side_sets", &File::read_side_sets) + .def("read_side_set_names", &File::read_side_set_names) + .def("read_times", &File::read_times) + // write + .def("write_coords", + static_cast &)>(&File::write_coords)) + .def("write_coords", + static_cast &, const std::vector &)>( + &File::write_coords)) + .def("write_coords", + static_cast &, + const std::vector &, + const std::vector &)>(&File::write_coords)) + .def("write_coord_names", static_cast(&File::write_coord_names)) + .def( + "write_coord_names", + static_cast &)>(&File::write_coord_names)) + .def("write_info", &File::write_info) + .def("write_time", &File::write_time) + .def("write_node_set_names", &File::write_node_set_names) + .def("write_node_set", &File::write_node_set) + .def("write_side_set_names", &File::write_side_set_names) + .def("write_side_set", &File::write_side_set) + .def("write_block_names", &File::write_block_names) + .def("write_block", &File::write_block) + .def("write_nodal_var_names", &File::write_nodal_var_names) + .def("write_elem_var_names", &File::write_elem_var_names) + .def("write_global_var_names", &File::write_global_var_names) + .def("write_nodal_var", &File::write_nodal_var) + .def("write_partial_nodal_var", &File::write_partial_nodal_var) + .def("write_partial_elem_var", &File::write_partial_elem_var) + .def("write_global_var", &File::write_global_var) + // + .def("update", &File::update) + .def("close", &File::close); +} diff --git a/python/src/exodusIIcpp/__init__.py b/python/src/exodusIIcpp/__init__.py new file mode 100644 index 0000000..c0e33a2 --- /dev/null +++ b/python/src/exodusIIcpp/__init__.py @@ -0,0 +1,5 @@ +from .exodusIIcpp import * + +__all__ = [ + File +] diff --git a/python/src/version.h.in b/python/src/version.h.in new file mode 100644 index 0000000..e58c556 --- /dev/null +++ b/python/src/version.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define EXODUSIICPP_VERSION "@CMAKE_PROJECT_VERSION_MAJOR@.@CMAKE_PROJECT_VERSION_MINOR@.0"