diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index 6e691de18..c34e99bba 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Create & Deploy Docs - uses: DenverCoder1/doxygen-github-pages-action@v1.3.1 + uses: DenverCoder1/doxygen-github-pages-action@v2.0.0 with: github_token: ${{secrets.GITHUB_TOKEN}} branch: docs diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 000000000..9dd7538cd --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,102 @@ +name: Build Wheels +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + inputs: + release: + type: boolean + required: true + default: false + description: 'Push the wheels to PyPI' + release: + types: [published] +jobs: + build_sdist: + name: Build SDist + runs-on: ubuntu-latest + defaults: + run: + working-directory: '${{github.workspace}}/lang/python' + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build SDist + run: | + pipx run build --sdist + + - name: Check Metadata + run: | + pipx run twine check dist/* + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: dist-sdist + path: ${{github.workspace}}/lang/python/dist/*.tar.gz + +# Linux build fails because the compiler's not new enough +# macOS build fails because it keeps trying to build a universal binary +# +# build_wheels: +# strategy: +# fail-fast: false +# matrix: +# os: [windows-latest, ubuntu-latest, macos-latest] +# name: Build Wheels For ${{matrix.os}} +# runs-on: ${{matrix.os}} +# steps: +# - name: Checkout Repository +# uses: actions/checkout@v4 +# with: +# submodules: true +# +# - name: Build Wheels +# uses: pypa/cibuildwheel@v2.19 +# with: +# package-dir: '${{github.workspace}}/lang/python' +# output-dir: '${{github.workspace}}/lang/python/wheelhouse' +# +# - name: Verify Clean Directory +# shell: bash +# run: | +# git diff --exit-code +# +# - name: Upload Wheels +# uses: actions/upload-artifact@v4 +# with: +# path: ${{github.workspace}}/lang/python/wheelhouse/*.whl +# name: dist-${{matrix.os}} + + merge_wheels: + name: Merge Wheels + runs-on: ubuntu-latest + needs: + - build_sdist +# - build_wheels + steps: + - name: Merge Artifacts + uses: actions/upload-artifact/merge@v4 + with: + name: dist + pattern: dist-* + + upload_release: + name: Upload a Release + if: (github.event_name == 'release' && github.event.action == 'published') || (github.event_name == 'workflow_dispatch' && inputs.release) + needs: [merge_wheels] + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + - uses: actions/download-artifact@v4 + with: + path: dist + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{secrets.PYPI_TOKEN}} diff --git a/.gitignore b/.gitignore index e49a35d76..148b32c84 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,14 @@ cmake-build-*/ out/ # Generated -__pycache__/ docs/html/ test/res/ test/Helpers.h + +# Python +.mypy_cache/ +__pycache__/ +*.pyd +*.pyi +*.typed +*.whl diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fec05017..ddb12ba6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.25 FATAL_ERROR) # Set defaults before project call if(PROJECT_IS_TOP_LEVEL) - set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") + set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE INTERNAL "" FORCE) endif() @@ -101,15 +101,16 @@ endif() # Python bindings, part 1 if(SOURCEPP_BUILD_PYTHON_WRAPPERS) set(SOURCEPP_PYTHON_NAME "${PROJECT_NAME}_python") - find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + find_package(Python REQUIRED + COMPONENTS Interpreter Development.Module + OPTIONAL_COMPONENTS Development.SABIModule) FetchContent_Declare( - pybind11 - GIT_REPOSITORY "https://github.com/pybind/pybind11.git" - GIT_TAG "v2.13.6") - FetchContent_MakeAvailable(pybind11) + nanobind + GIT_REPOSITORY "https://github.com/wjakob/nanobind.git" + GIT_TAG "origin/master") + FetchContent_MakeAvailable(nanobind) set(${SOURCEPP_PYTHON_NAME}_SOURCES "") set(${SOURCEPP_PYTHON_NAME}_DEFINES "") - list(APPEND ${SOURCEPP_PYTHON_NAME}_DEPS pybind11::headers) endif() @@ -143,7 +144,6 @@ endif() # Benchmarks if(SOURCEPP_BUILD_BENCHMARKS) set(SOURCEPP_BENCH_NAME "${PROJECT_NAME}_bench") - include(FetchContent) FetchContent_Declare( benchmark GIT_REPOSITORY https://github.com/google/benchmark.git @@ -182,16 +182,52 @@ endif() # Python bindings, part 2 if(SOURCEPP_BUILD_PYTHON_WRAPPERS) - python_add_library(${SOURCEPP_PYTHON_NAME} MODULE "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp.cpp" ${${SOURCEPP_PYTHON_NAME}_SOURCES} WITH_SOABI) - set_target_properties(${SOURCEPP_PYTHON_NAME} PROPERTIES PREFIX "_" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/dist/sourcepp") + nanobind_add_module(${SOURCEPP_PYTHON_NAME} NB_STATIC STABLE_ABI LTO + "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp.cpp" + ${${SOURCEPP_PYTHON_NAME}_SOURCES}) + set_target_properties(${SOURCEPP_PYTHON_NAME} PROPERTIES + OUTPUT_NAME "_${PROJECT_NAME}_impl" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp" + LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp") target_compile_definitions(${SOURCEPP_PYTHON_NAME} PRIVATE ${${SOURCEPP_PYTHON_NAME}_DEFINES}) target_link_libraries(${SOURCEPP_PYTHON_NAME} PRIVATE ${${SOURCEPP_PYTHON_NAME}_DEPS}) + + add_custom_target(${SOURCEPP_PYTHON_NAME}_all) + add_dependencies(${SOURCEPP_PYTHON_NAME}_all ${SOURCEPP_PYTHON_NAME}) + + # We need to manually write out each module :( + set(${SOURCEPP_PYTHON_NAME}_MODULES + "sourcepp" + "sourcepp._sourcepp_impl" + "sourcepp._sourcepp_impl.gamepp" + "sourcepp._sourcepp_impl.sourcepp" + "sourcepp._sourcepp_impl.sourcepp.math" + "sourcepp._sourcepp_impl.steampp" + "sourcepp._sourcepp_impl.vcryptpp" + "sourcepp._sourcepp_impl.vcryptpp.VFONT" + "sourcepp._sourcepp_impl.vcryptpp.VICE" + "sourcepp._sourcepp_impl.vtfpp" + "sourcepp._sourcepp_impl.vtfpp.ImageFormatDetails" + "sourcepp._sourcepp_impl.vtfpp.ImageDimensions" + "sourcepp._sourcepp_impl.vtfpp.ImageConversion") + foreach(MODULE ${${SOURCEPP_PYTHON_NAME}_MODULES}) + string(REPLACE "." "/" MODULE_DIR "${MODULE}") + string(REPLACE "." "_" MODULE_NAME_NORMALIZED "${MODULE}") + set(MODULE_NAME_NORMALIZED "${MODULE_NAME_NORMALIZED}_stub") + nanobind_add_stub("${SOURCEPP_PYTHON_NAME}_stub_${MODULE_NAME_NORMALIZED}" + DEPENDS ${SOURCEPP_PYTHON_NAME} + MODULE "${MODULE}" + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/${MODULE_DIR}.pyi" + PYTHON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src") + add_dependencies(${SOURCEPP_PYTHON_NAME}_all ${SOURCEPP_PYTHON_NAME}_stub_${MODULE_NAME_NORMALIZED}) + endforeach() endif() # Print options print_options(OPTIONS USE_BSPPP USE_DMXPP USE_GAMEPP USE_KVPP USE_MDLPP USE_STEAMPP USE_TOOLPP USE_VCRYPTPP USE_VPKPP USE_VTFPP - BUILD_BENCHMARKS BUILD_C_WRAPPERS BUILD_PYTHON_WRAPPERS BUILD_WITH_OPENCL BUILD_WITH_TBB BUILD_WITH_THREADS BUILD_TESTS BUILD_WIN7_COMPAT + BUILD_BENCHMARKS BUILD_C_WRAPPERS BUILD_CSHARP_WRAPPERS BUILD_PYTHON_WRAPPERS BUILD_WITH_OPENCL BUILD_WITH_TBB BUILD_WITH_THREADS BUILD_TESTS BUILD_WIN7_COMPAT LINK_STATIC_MSVC_RUNTIME VPKPP_SUPPORT_VPK_V54) diff --git a/README.md b/README.md index 310ccd39d..6fc49a144 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one - bsppp + bsppp* BSP v17-27 ✅ ✅ @@ -30,7 +30,7 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one - dmxpp + dmxpp* DMX Binary v1-5 ✅ ❌ @@ -53,15 +53,15 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one kvpp - KeyValues v1* + KeyValues Text v1 ✅ ✅ - mdlpp - MDL v44-49 + mdlpp* + MDL v44-49 ✅ ❌ @@ -290,9 +290,16 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one -(\*) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VDF](https://developer.valvesoftware.com/wiki/VDF), [VMT](https://developer.valvesoftware.com/wiki/VMT), and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)). +(\*) These libraries are incomplete and still in development. Their interfaces are unstable and will likely change in the future. +Libraries not starred should be considered stable, and their existing interfaces will not change much if at all. Note that wrappers +only exist for stable libraries. -(†) The MDL parser is not complete. It is usable in its current state, but it does not currently parse more complex components like animations. This parser is still in development. +(†) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VMT](https://developer.valvesoftware.com/wiki/VMT) and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)). + +## Wrappers + +Wrappers for libraries considered complete exist for C, C#, and/or Python, depending on the library. The Python wrappers can be +found on PyPI in the `sourcepp` package. ## Special Thanks diff --git a/THIRDPARTY_LEGAL_NOTICES.txt b/THIRDPARTY_LEGAL_NOTICES.txt index 44cfc395a..a6d198797 100644 --- a/THIRDPARTY_LEGAL_NOTICES.txt +++ b/THIRDPARTY_LEGAL_NOTICES.txt @@ -222,6 +222,37 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. +--------------- nanobind --------------- + +Copyright (c) 2022 Wenzel Jakob . +All rights reserved. + +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. + + --------------- stb --------------- Copyright (c) 2017 Sean Barrett diff --git a/docs/index.md b/docs/index.md index 67d772be6..1217602e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,14 +22,14 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one Wrappers - bsppp + bsppp* BSP v17-27 ✅ ✅ - dmxpp + dmxpp* DMX Binary v1-5 ✅ ❌ @@ -49,14 +49,14 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one kvpp - KeyValues v1* + KeyValues Text v1 ✅ ✅ - mdlpp - MDL v44-49 + mdlpp* + MDL v44-49 ✅ ❌ @@ -253,9 +253,16 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one \endhtmlonly -(\*) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VDF](https://developer.valvesoftware.com/wiki/VDF), [VMT](https://developer.valvesoftware.com/wiki/VMT), and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)). +(\*) These libraries are incomplete and still in development. Their interfaces are unstable and will likely change in the future. +Libraries not starred should be considered stable, and their existing interfaces will not change much if at all. Note that wrappers +only exist for stable libraries. -(†) The MDL parser is not complete. It is usable in its current state, but it does not currently parse more complex components like animations. This parser is still in development. +(†) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VMT](https://developer.valvesoftware.com/wiki/VMT) and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)). + +## Wrappers + +Wrappers for libraries considered complete exist for C, C#, and/or Python, depending on the library. The Python wrappers can be +found on PyPI in the `sourcepp` package. ## Special Thanks diff --git a/lang/python/.gitignore b/lang/python/.gitignore index 0c986d628..d88da90dd 100644 --- a/lang/python/.gitignore +++ b/lang/python/.gitignore @@ -1,4 +1,8 @@ -# Build Files -__pycache__/ -dist/sourcepp/* -!dist/sourcepp/__init__.py +# Keep most Python-specific stuff in the root .gitignore +# because scikit-build-core will use it to ignore everything +# you want to ship!!!!!!!! This took like an hour to figure +# out, thanks folks :D + +# Anyway + +wheelhouse/ diff --git a/lang/python/CMakeLists.txt b/lang/python/CMakeLists.txt new file mode 100644 index 000000000..069cb3677 --- /dev/null +++ b/lang/python/CMakeLists.txt @@ -0,0 +1,39 @@ +# Load this to build the sourcepp Python package + +cmake_minimum_required(VERSION 3.25 FATAL_ERROR) +set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE INTERNAL "" FORCE) +project(sourcepp_python) + +if (NOT SKBUILD) + message(WARNING "\ +This CMake file is meant to be executed using 'scikit-build-core'. +Running it directly will almost certainly not produce the desired +result. If you are a user trying to install this package, use the +command below, which will install all necessary build dependencies, +compile the package in an isolated environment, and then install it. +===================================================================== + $ pip install . +===================================================================== +If you are a software developer, and this is your own package, then +it is usually much more efficient to install the build dependencies +in your environment once and use the following command that avoids +a costly creation of a new virtual environment at every compilation: +===================================================================== + $ pip install nanobind scikit-build-core[pyproject] + $ pip install --no-build-isolation -ve . +===================================================================== +You may optionally add -Ceditable.rebuild=true to auto-rebuild when +the package is imported. Otherwise, you need to rerun the above +after editing C++ files.") +endif() + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# As weird as this looks, this is necessary for sdist wheel +include(FetchContent) +FetchContent_Declare( + sourcepp + GIT_REPOSITORY "https://github.com/craftablescience/sourcepp.git" + GIT_TAG "origin/main") +set(SOURCEPP_BUILD_PYTHON_WRAPPERS ON CACHE INTERNAL "" FORCE) +FetchContent_MakeAvailable(sourcepp) diff --git a/lang/python/dist/sourcepp/__init__.py b/lang/python/dist/sourcepp/__init__.py deleted file mode 100644 index 458686cfc..000000000 --- a/lang/python/dist/sourcepp/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from ._sourcepp_python import __author__, __doc__, __version__, gamepp, sourcepp, steampp, vcryptpp, vtfpp -__all__ = ['__author__', '__doc__', '__version__', 'gamepp', 'sourcepp', 'steampp', 'vcryptpp', 'vtfpp'] diff --git a/lang/python/pyproject.toml b/lang/python/pyproject.toml new file mode 100644 index 000000000..e086660d6 --- /dev/null +++ b/lang/python/pyproject.toml @@ -0,0 +1,53 @@ +[build-system] +requires = ["scikit-build-core >=0.10.7", "nanobind >=1.3.2"] +build-backend = "scikit_build_core.build" + + +[project] +name = "sourcepp" +version = "2024.11.5dev1" # change __init__.py when this is updated +authors = [{ name = "craftablescience", email = "lauralewisdev@gmail.com" }] +maintainers = [{ name = "craftablescience", email = "lauralewisdev@gmail.com" }] +description = "Several modern C++20 libraries for sanely parsing Valve formats." +readme = "../../README.md" +requires-python = ">=3.8" +classifiers = [ + "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[project.urls] +"homepage" = "https://github.com/craftablescience/sourcepp" +"repository" = "https://github.com/craftablescience/sourcepp" +"issue tracker" = "https://github.com/craftablescience/sourcepp/issues" +"funding" = "https://ko-fi.com/craftablescience" + +[tool.scikit-build] +minimum-version = "build-system.requires" +build-dir = "build/{wheel_tag}" +build.targets = ["sourcepp_python_all"] +wheel.py-api = "cp312" +wheel.license-files = ["../../LICENSE", "../../THIRDPARTY_LEGAL_NOTICES.txt"] +build.verbose = true +logging.level = "INFO" + + +[tool.cibuildwheel] +archs = ["auto64"] +build-verbosity = 1 +#test-command = "pytest {project}/test" +#test-requires = "pytest" +#test-skip="cp38-macosx_*:arm64" + +[tool.cibuildwheel.macos] +archs = ["arm64"] + +[tool.cibuildwheel.macos.environment] +MACOSX_DEPLOYMENT_TARGET = "14.7" diff --git a/lang/python/src/gamepp.h b/lang/python/src/gamepp.h index c5a4720ca..8ac53f9a7 100644 --- a/lang/python/src/gamepp.h +++ b/lang/python/src/gamepp.h @@ -1,9 +1,11 @@ #pragma once -#include -#include +#include +#include +#include +#include -namespace py = pybind11; +namespace py = nanobind; #include @@ -14,16 +16,16 @@ inline void register_python(py::module_& m) { using namespace gamepp; py::class_(gamepp, "GameInstance") - .def_static("find", &GameInstance::find, py::arg("window_name_override") = "") - .def_property_readonly("window_title", &GameInstance::getWindowTitle) - .def_property_readonly("window_pos", &GameInstance::getWindowPos) - .def_property_readonly("window_size", &GameInstance::getWindowSize) - .def("command", &GameInstance::command, py::arg("command")) - .def("input_begin", &GameInstance::inputBegin, py::arg("input")) - .def("input_end", &GameInstance::inputEnd, py::arg("input")) - .def("input_once", &GameInstance::inputOnce, py::arg("input")) - .def("input_hold", &GameInstance::inputHold, py::arg("input"), py::arg("sec")) - .def("wait", &GameInstance::wait, py::arg("sec")); + .def_static("find", &GameInstance::find, py::arg("window_name_override") = "") + .def_prop_ro("window_title", &GameInstance::getWindowTitle) + .def_prop_ro("window_pos", &GameInstance::getWindowPos) + .def_prop_ro("window_size", &GameInstance::getWindowSize) + .def("command", &GameInstance::command, py::arg("command")) + .def("input_begin", &GameInstance::inputBegin, py::arg("input")) + .def("input_end", &GameInstance::inputEnd, py::arg("input")) + .def("input_once", &GameInstance::inputOnce, py::arg("input")) + .def("input_hold", &GameInstance::inputHold, py::arg("input"), py::arg("sec")) + .def("wait", &GameInstance::wait, py::arg("sec")); } } // namespace gamepp diff --git a/lang/python/src/sourcepp.cpp b/lang/python/src/sourcepp.cpp index 813fe75e1..ff7935af8 100644 --- a/lang/python/src/sourcepp.cpp +++ b/lang/python/src/sourcepp.cpp @@ -16,12 +16,9 @@ #include "vtfpp.h" #endif -PYBIND11_MODULE(_sourcepp_python, m) { +NB_MODULE(_sourcepp_impl, m) { m.doc() = "SourcePP: A Python wrapper around several modern C++20 libraries for sanely parsing Valve's formats."; - m.attr("__author__") = "craftablescience"; - m.attr("__version__") = "dev"; - sourcepp::register_python(m); #ifdef GAMEPP diff --git a/lang/python/src/sourcepp.h b/lang/python/src/sourcepp.h index b2908ae0e..079c37ae0 100644 --- a/lang/python/src/sourcepp.h +++ b/lang/python/src/sourcepp.h @@ -2,11 +2,10 @@ #include -#include -#include -#include +#include +#include -namespace py = pybind11; +namespace py = nanobind; #include @@ -21,10 +20,7 @@ inline void register_python(py::module_& m) { using namespace math; const auto registerVecType = [&math](std::string_view name) { - py::class_(math, name.data(), pybind11::buffer_protocol()) - .def_buffer([](V& v) -> py::buffer_info { - return {v.data(), static_cast(v.size())}; - }) + py::class_(math, name.data()) .def("__len__", &V::size) .def("__setitem__", [](V& self, uint8_t index, typename V::value_type val) { self[index] = val; }) .def("__getitem__", [](V& self, uint8_t index) { return self[index]; }) diff --git a/lang/python/src/sourcepp/__init__.py b/lang/python/src/sourcepp/__init__.py new file mode 100644 index 000000000..2cac8b428 --- /dev/null +++ b/lang/python/src/sourcepp/__init__.py @@ -0,0 +1,5 @@ +from ._sourcepp_impl import __doc__, gamepp, sourcepp, steampp, vcryptpp, vtfpp + +__author__ = "craftablescience" +__version__ = "2024.11.5dev1" # change pyproject.toml version when this is updated +__all__ = ['__author__', '__doc__', '__version__', 'gamepp', 'sourcepp', 'steampp', 'vcryptpp', 'vtfpp'] diff --git a/lang/python/src/steampp.h b/lang/python/src/steampp.h index 4ef0ccfae..b1ff5d7fa 100644 --- a/lang/python/src/steampp.h +++ b/lang/python/src/steampp.h @@ -1,9 +1,11 @@ #pragma once -#include -#include +#include +#include +#include +#include -namespace py = pybind11; +namespace py = nanobind; #include @@ -15,20 +17,20 @@ inline void register_python(py::module_& m) { py::class_(steampp, "Steam") .def(py::init<>()) - .def_property_readonly("install_dir", &Steam::getInstallDir) - .def_property_readonly("library_dirs", &Steam::getLibraryDirs) - .def_property_readonly("sourcemod_dir", &Steam::getSourceModDir) - .def_property_readonly("installed_apps", &Steam::getInstalledApps) - .def("is_app_installed", &Steam::isAppInstalled, py::arg("appID")) - .def("get_app_name", &Steam::getAppName, py::arg("appID")) - .def("get_app_install_dir", &Steam::getAppInstallDir, py::arg("appID")) - .def("get_app_icon_path", &Steam::getAppIconPath, py::arg("appID")) - .def("get_app_logo_path", &Steam::getAppLogoPath, py::arg("appID")) - .def("get_app_box_art_path", &Steam::getAppBoxArtPath, py::arg("appID")) - .def("get_app_store_art_path", &Steam::getAppStoreArtPath, py::arg("appID")) - .def("is_app_using_source_engine", &Steam::isAppUsingSourceEngine, py::arg("appID")) - .def("is_app_using_source_2_engine", &Steam::isAppUsingSource2Engine, py::arg("appID")) - .def_property_readonly("__bool__", &Steam::operator bool, py::is_operator()); + .def_prop_ro("install_dir", &Steam::getInstallDir) + .def_prop_ro("library_dirs", &Steam::getLibraryDirs) + .def_prop_ro("sourcemod_dir", &Steam::getSourceModDir) + .def_prop_ro("installed_apps", &Steam::getInstalledApps) + .def("is_app_installed", &Steam::isAppInstalled, py::arg("appID")) + .def("get_app_name", &Steam::getAppName, py::arg("appID")) + .def("get_app_install_dir", &Steam::getAppInstallDir, py::arg("appID")) + .def("get_app_icon_path", &Steam::getAppIconPath, py::arg("appID")) + .def("get_app_logo_path", &Steam::getAppLogoPath, py::arg("appID")) + .def("get_app_box_art_path", &Steam::getAppBoxArtPath, py::arg("appID")) + .def("get_app_store_art_path", &Steam::getAppStoreArtPath, py::arg("appID")) + .def("is_app_using_source_engine", &Steam::isAppUsingSourceEngine, py::arg("appID")) + .def("is_app_using_source_2_engine", &Steam::isAppUsingSource2Engine, py::arg("appID")) + .def_prop_ro("__bool__", &Steam::operator bool, py::is_operator()); } } // namespace steampp diff --git a/lang/python/src/vcryptpp.h b/lang/python/src/vcryptpp.h index 418ffc8af..1ae642783 100644 --- a/lang/python/src/vcryptpp.h +++ b/lang/python/src/vcryptpp.h @@ -1,9 +1,9 @@ #pragma once -#include -#include +#include +#include -namespace py = pybind11; +namespace py = nanobind; #include @@ -21,10 +21,9 @@ inline void register_python(py::module_& m) { VFONT.attr("MAGIC") = MAGIC; - VFONT.def("decrypt_bytes", [](const py::bytes& data) -> py::bytes { - const std::string_view dataView{data}; - const auto d = decrypt({reinterpret_cast(dataView.data()), dataView.size()}); - return {reinterpret_cast(d.data()), d.size()}; + VFONT.def("decrypt_bytes", [](const py::bytes& data) { + const auto d = decrypt({reinterpret_cast(data.data()), data.size()}); + return py::bytes{d.data(), d.size()}; }, py::arg("data")); } @@ -66,10 +65,9 @@ inline void register_python(py::module_& m) { KnownCodes.attr("EKV_GPU_PORTAL_2") = EKV_GPU_PORTAL_2; } - VICE.def("decrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) -> py::bytes { - const std::string_view dataView{data}; - const auto d = decrypt({reinterpret_cast(dataView.data()), dataView.size()}, code); - return {reinterpret_cast(d.data()), d.size()}; + VICE.def("decrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) { + const auto d = decrypt({reinterpret_cast(data.data()), data.size()}, code); + return py::bytes{d.data(), d.size()}; }, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT); VICE.def("decrypt_str", [](std::string_view data, std::string_view code = KnownCodes::DEFAULT) -> std::string { @@ -77,15 +75,14 @@ inline void register_python(py::module_& m) { return {reinterpret_cast(d.data()), d.size()}; }, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT); - VICE.def("encrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) -> py::bytes { - const std::string_view dataView{data}; - const auto e = encrypt({reinterpret_cast(dataView.data()), dataView.size()}, code); - return {reinterpret_cast(e.data()), e.size()}; + VICE.def("encrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) { + const auto d = encrypt({reinterpret_cast(data.data()), data.size()}, code); + return py::bytes{d.data(), d.size()}; }, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT); VICE.def("encrypt_str", [](std::string_view data, std::string_view code = KnownCodes::DEFAULT) -> std::string { - const auto e = decrypt({reinterpret_cast(data.data()), data.size()}, code); - return {reinterpret_cast(e.data()), e.size()}; + const auto d = encrypt({reinterpret_cast(data.data()), data.size()}, code); + return {reinterpret_cast(d.data()), d.size()}; }, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT); } } diff --git a/lang/python/src/vtfpp.h b/lang/python/src/vtfpp.h index 631b1a83a..05a4b7a0e 100644 --- a/lang/python/src/vtfpp.h +++ b/lang/python/src/vtfpp.h @@ -2,10 +2,13 @@ #include -#include -#include +#include +#include +#include +#include +#include -namespace py = pybind11; +namespace py = nanobind; #include @@ -106,16 +109,14 @@ void register_python(py::module_& m) { auto ImageConversion = vtfpp.def_submodule("ImageConversion"); // todo(python): still need to bind the following: - ImageConversion.def("convert_image_data_to_format", [](const py::bytes& imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height) -> py::bytes { - const std::string_view imageDataView{imageData}; - const auto d = convertImageDataToFormat({reinterpret_cast(imageDataView.data()), imageDataView.size()}, oldFormat, newFormat, width, height); - return {reinterpret_cast(d.data()), d.size()}; + ImageConversion.def("convert_image_data_to_format", [](const py::bytes& imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height) { + const auto d = convertImageDataToFormat({reinterpret_cast(imageData.data()), imageData.size()}, oldFormat, newFormat, width, height); + return py::bytes{d.data(), d.size()}; }, py::arg("image_data"), py::arg("old_format"), py::arg("new_format"), py::arg("width"), py::arg("height")); - ImageConversion.def("convert_several_image_data_to_format", [](const py::bytes& imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint16_t faceCount, uint16_t width, uint16_t height, uint16_t sliceCount) -> py::bytes { - const std::string_view imageDataView{imageData}; - const auto d = convertSeveralImageDataToFormat({reinterpret_cast(imageDataView.data()), imageDataView.size()}, oldFormat, newFormat, mipCount, frameCount, faceCount, width, height, sliceCount); - return {reinterpret_cast(d.data()), d.size()}; + ImageConversion.def("convert_several_image_data_to_format", [](const py::bytes& imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint16_t faceCount, uint16_t width, uint16_t height, uint16_t sliceCount) { + const auto d = convertSeveralImageDataToFormat({reinterpret_cast(imageData.data()), imageData.size()}, oldFormat, newFormat, mipCount, frameCount, faceCount, width, height, sliceCount); + return py::bytes{d.data(), d.size()}; }, py::arg("image_data"), py::arg("old_format"), py::arg("new_format"), py::arg("mip_count"), py::arg("frame_count"), py::arg("face_count"), py::arg("width"), py::arg("height"), py::arg("slice_count")); py::enum_(ImageConversion, "FileFormat") @@ -129,18 +130,16 @@ void register_python(py::module_& m) { ImageConversion.def("get_default_file_format_for_image_format", &getDefaultFileFormatForImageFormat, py::arg("format")); - ImageConversion.def("convert_image_data_to_file", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat = FileFormat::DEFAULT) -> py::bytes { - const std::string_view imageDataView{imageData}; - const auto d = convertImageDataToFile({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, height, fileFormat); - return {reinterpret_cast(d.data()), d.size()}; + ImageConversion.def("convert_image_data_to_file", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat = FileFormat::DEFAULT) { + const auto d = convertImageDataToFile({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, fileFormat); + return py::bytes{d.data(), d.size()}; }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("file_format") = FileFormat::DEFAULT); ImageConversion.def("convert_file_to_image_data", [](const py::bytes& fileData) -> std::tuple { - const std::string_view fileDataView{fileData}; ImageFormat format; int width, height, frame; - const auto d = convertFileToImageData({reinterpret_cast(fileDataView.data()), fileDataView.size()}, format, width, height, frame); - return {{reinterpret_cast(d.data()), d.size()}, format, width, height, frame}; + const auto d = convertFileToImageData({reinterpret_cast(fileData.data()), fileData.size()}, format, width, height, frame); + return {py::bytes{d.data(), d.size()}, format, width, height, frame}; }, py::arg("file_data")); py::enum_(ImageConversion, "ResizeEdge") @@ -172,45 +171,39 @@ void register_python(py::module_& m) { return {width, height}; }, py::arg("width"), py::arg("resize_width"), py::arg("height"), py::arg("resize_height")); - ImageConversion.def("resize_image_data", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, ResizeFilter filter, ResizeEdge edge = ResizeEdge::CLAMP) -> py::bytes { - const std::string_view imageDataView{imageData}; - const auto d = resizeImageData({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, newWidth, height, newHeight, srgb, filter, edge); - return {reinterpret_cast(d.data()), d.size()}; + ImageConversion.def("resize_image_data", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, ResizeFilter filter, ResizeEdge edge = ResizeEdge::CLAMP) { + const auto d = resizeImageData({reinterpret_cast(imageData.data()), imageData.size()}, format, width, newWidth, height, newHeight, srgb, filter, edge); + return py::bytes{d.data(), d.size()}; }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("new_width"), py::arg("height"), py::arg("new_height"), py::arg("srgb"), py::arg("filter"), py::arg("edge") = ResizeEdge::CLAMP); ImageConversion.def("resize_image_data_strict", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t newWidth, ResizeMethod widthResize, uint16_t height, uint16_t newHeight, ResizeMethod heightResize, bool srgb, ResizeFilter filter, ResizeEdge edge = ResizeEdge::CLAMP) -> std::tuple { - const std::string_view imageDataView{imageData}; uint16_t widthOut, heightOut; - const auto d = resizeImageDataStrict({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, newWidth, widthOut, widthResize, height, newHeight, heightOut, heightResize, srgb, filter, edge); - return {{reinterpret_cast(d.data()), d.size()}, widthOut, heightOut}; + const auto d = resizeImageDataStrict({reinterpret_cast(imageData.data()), imageData.size()}, format, width, newWidth, widthOut, widthResize, height, newHeight, heightOut, heightResize, srgb, filter, edge); + return {py::bytes{d.data(), d.size()}, widthOut, heightOut}; }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("new_width"), py::arg("width_resize"), py::arg("height"), py::arg("new_height"), py::arg("height_resize"), py::arg("srgb"), py::arg("filter"), py::arg("edge") = ResizeEdge::CLAMP); // Skip extractChannelFromImageData, difficult to bind } py::class_(vtfpp, "PPLImage") - .def_readonly("width", &PPL::Image::width) - .def_readonly("height", &PPL::Image::height) - .def_property("data", [](const PPL::Image& self) -> py::bytes { - return {reinterpret_cast(self.data.data()), self.data.size()}; - }, [](PPL::Image& self, const py::bytes& data) { - const std::string_view dataView{data}; - self.data = {reinterpret_cast(dataView.data()), reinterpret_cast(dataView.data()) + dataView.size()}; + .def_ro("width", &PPL::Image::width) + .def_ro("height", &PPL::Image::height) + .def_prop_ro("data", [](const PPL::Image& self) { + return py::bytes{self.data.data(), self.data.size()}; }); py::class_(vtfpp, "PPL") .def(py::init(), py::arg("checksum"), py::arg("format") = ImageFormat::RGB888, py::arg("version") = 0) - .def(py::init([](const py::bytes& pplData) -> PPL* { - const std::string_view pplDataView{pplData}; - return new PPL{{reinterpret_cast(pplDataView.data()), pplDataView.size()}}; - }), py::arg("ppl_data")) + .def("__init__", [](PPL* self, const py::bytes& pplData) { + return new(self) PPL{{reinterpret_cast(pplData.data()), pplData.size()}}; + }, py::arg("ppl_data")) .def(py::init(), py::arg("path")) .def("__bool__", &PPL::operator bool, py::is_operator()) - .def_property("version", &PPL::getVersion, &PPL::setVersion) - .def_property("checksum", &PPL::getChecksum, &PPL::setChecksum) - .def_property("format", &PPL::getFormat, &PPL::setFormat) + .def_prop_rw("version", &PPL::getVersion, &PPL::setVersion) + .def_prop_rw("checksum", &PPL::getChecksum, &PPL::setChecksum) + .def_prop_rw("format", &PPL::getFormat, &PPL::setFormat) .def("has_image_for_lod", &PPL::hasImageForLOD, py::arg("lod")) - .def_property_readonly("image_lods", &PPL::getImageLODs) + .def_prop_ro("image_lods", &PPL::getImageLODs) .def("get_image_raw", [](const PPL& self, uint32_t lod = 0) -> std::optional { const auto* image = self.getImageRaw(lod); if (!image) { @@ -221,23 +214,21 @@ void register_python(py::module_& m) { .def("get_image_as", &PPL::getImageAs, py::arg("new_format"), py::arg("lod")) .def("get_image_as_rgb888", &PPL::getImageAsRGB888, py::arg("lod")) .def("set_image", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t lod = 0) { - const std::string_view imageDataView{imageData}; - self.setImage({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, height, lod); + self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, lod); }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("lod") = 0) .def("set_image_resized", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR) { - const std::string_view imageDataView{imageData}; - self.setImage({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, height, resizedWidth, resizedHeight, lod, filter); + self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, resizedWidth, resizedHeight, lod, filter); }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) .def("set_image_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("lod") = 0) .def("set_image_resized_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def("save_image", [](const PPL& self, uint32_t lod = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) -> py::bytes { + .def("save_image", [](const PPL& self, uint32_t lod = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { const auto d = self.saveImageToFile(lod, fileFormat); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }, py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) .def("save_image_to_file", py::overload_cast(&PPL::saveImageToFile, py::const_), py::arg("image_path"), py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("bake", [](PPL& self) -> py::bytes { + .def("bake", [](PPL& self) { const auto d = self.bake(); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }) .def("bake_to_file", py::overload_cast(&PPL::bake), py::arg("ppl_path")); @@ -306,84 +297,78 @@ void register_python(py::module_& m) { py::class_(vtfpp, "VTFCreationOptions") .def(py::init<>()) - .def_readwrite("major_version", &VTF::CreationOptions::majorVersion) - .def_readwrite("minor_version", &VTF::CreationOptions::minorVersion) - .def_readwrite("output_format", &VTF::CreationOptions::outputFormat) - .def_readwrite("width_resize_method", &VTF::CreationOptions::widthResizeMethod) - .def_readwrite("height_resize_method", &VTF::CreationOptions::heightResizeMethod) - .def_readwrite("filter", &VTF::CreationOptions::filter) - .def_readwrite("flags", &VTF::CreationOptions::flags) - .def_readwrite("initial_frame_count", &VTF::CreationOptions::initialFrameCount) - .def_readwrite("start_frame", &VTF::CreationOptions::startFrame) - .def_readwrite("is_cubemap", &VTF::CreationOptions::isCubeMap) - .def_readwrite("has_spheremap", &VTF::CreationOptions::hasSphereMap) - .def_readwrite("initial_slice_count", &VTF::CreationOptions::initialSliceCount) - .def_readwrite("create_mips", &VTF::CreationOptions::createMips) - .def_readwrite("create_thumbnail", &VTF::CreationOptions::createThumbnail) - .def_readwrite("create_reflectivity", &VTF::CreationOptions::createReflectivity) - .def_readwrite("compression_level", &VTF::CreationOptions::compressionLevel) - .def_readwrite("compression_method", &VTF::CreationOptions::compressionMethod) - .def_readwrite("bumpmap_scale", &VTF::CreationOptions::bumpMapScale); + .def_rw("major_version", &VTF::CreationOptions::majorVersion) + .def_rw("minor_version", &VTF::CreationOptions::minorVersion) + .def_rw("output_format", &VTF::CreationOptions::outputFormat) + .def_rw("width_resize_method", &VTF::CreationOptions::widthResizeMethod) + .def_rw("height_resize_method", &VTF::CreationOptions::heightResizeMethod) + .def_rw("filter", &VTF::CreationOptions::filter) + .def_rw("flags", &VTF::CreationOptions::flags) + .def_rw("initial_frame_count", &VTF::CreationOptions::initialFrameCount) + .def_rw("start_frame", &VTF::CreationOptions::startFrame) + .def_rw("is_cubemap", &VTF::CreationOptions::isCubeMap) + .def_rw("has_spheremap", &VTF::CreationOptions::hasSphereMap) + .def_rw("initial_slice_count", &VTF::CreationOptions::initialSliceCount) + .def_rw("create_mips", &VTF::CreationOptions::createMips) + .def_rw("create_thumbnail", &VTF::CreationOptions::createThumbnail) + .def_rw("create_reflectivity", &VTF::CreationOptions::createReflectivity) + .def_rw("compression_level", &VTF::CreationOptions::compressionLevel) + .def_rw("compression_method", &VTF::CreationOptions::compressionMethod) + .def_rw("bumpmap_scale", &VTF::CreationOptions::bumpMapScale); py::class_(vtfpp, "VTF") - .def_readonly_static("FLAG_MASK_GENERATED", &VTF::FLAG_MASK_GENERATED) - .def_readonly_static("FORMAT_UNCHANGED", &VTF::FORMAT_UNCHANGED) - .def_readonly_static("FORMAT_DEFAULT", &VTF::FORMAT_DEFAULT) - .def_readonly_static("MAX_RESOURCES", &VTF::MAX_RESOURCES) + .def_ro_static("FLAG_MASK_GENERATED", &VTF::FLAG_MASK_GENERATED) + .def_ro_static("FORMAT_UNCHANGED", &VTF::FORMAT_UNCHANGED) + .def_ro_static("FORMAT_DEFAULT", &VTF::FORMAT_DEFAULT) + .def_ro_static("MAX_RESOURCES", &VTF::MAX_RESOURCES) .def(py::init<>()) - .def(py::init([](const py::bytes& vtfData, bool parseHeaderOnly = false) -> VTF* { - const std::string_view vtfDataView{vtfData}; - return new VTF{std::span{reinterpret_cast(vtfDataView.data()), vtfDataView.size()}, parseHeaderOnly}; - }), py::arg("vtf_data"), py::arg("parse_header_only") = false) + .def("__init__", [](VTF* self, const py::bytes& vtfData, bool parseHeaderOnly = false) { + return new(self) VTF{std::span{reinterpret_cast(vtfData.data()), vtfData.size()}, parseHeaderOnly}; + }, py::arg("vtf_data"), py::arg("parse_header_only") = false) .def(py::init(), py::arg("vtf_path"), py::arg("parse_header_only") = false) .def("__bool__", &VTF::operator bool, py::is_operator()) .def_static("create_and_bake", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, VTF::CreationOptions options) { - const std::string_view imageDataView{imageData}; - VTF::create({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, height, vtfPath, options); + VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, vtfPath, options); }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) .def_static("create_blank_and_bake", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) .def_static("create", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, VTF::CreationOptions options) { - const std::string_view imageDataView{imageData}; - return VTF::create({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, height, options); + return VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, options); }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{}) .def_static("create_blank", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{}) .def_static("create_from_file_and_bake", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) .def_static("create_from_file", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("creation_options") = VTF::CreationOptions{}) - .def_property("version_major", &VTF::getMajorVersion, &VTF::setMajorVersion) - .def_property("version_minor", &VTF::getMinorVersion, &VTF::setMinorVersion) - .def_property("image_width_resize_method", &VTF::getImageWidthResizeMethod, &VTF::setImageWidthResizeMethod) - .def_property("image_height_resize_method", &VTF::getImageHeightResizeMethod, &VTF::setImageHeightResizeMethod) - .def_property_readonly("width", &VTF::getWidth) + .def_prop_rw("version_major", &VTF::getMajorVersion, &VTF::setMajorVersion) + .def_prop_rw("version_minor", &VTF::getMinorVersion, &VTF::setMinorVersion) + .def_prop_rw("image_width_resize_method", &VTF::getImageWidthResizeMethod, &VTF::setImageWidthResizeMethod) + .def_prop_rw("image_height_resize_method", &VTF::getImageHeightResizeMethod, &VTF::setImageHeightResizeMethod) + .def_prop_ro("width", &VTF::getWidth) .def("width_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getWidth(mip); }, py::arg("mip") = 0) - .def_property_readonly("height", &VTF::getHeight) + .def_prop_ro("height", &VTF::getHeight) .def("height_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getHeight(mip); }, py::arg("mip") = 0) .def("set_size", &VTF::setSize, py::arg("width"), py::arg("height"), py::arg("filter")) - .def_property("flags", &VTF::getFlags, &VTF::setFlags) + .def_prop_rw("flags", &VTF::getFlags, &VTF::setFlags) .def("add_flags", &VTF::addFlags, py::arg("flags")) .def("remove_flags", &VTF::removeFlags, py::arg("flags")) - .def_property_readonly("format", &VTF::getFormat) + .def_prop_ro("format", &VTF::getFormat) .def("set_format", &VTF::setFormat, py::arg("new_format"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def_property("mip_count", &VTF::getMipCount, &VTF::setMipCount) + .def_prop_rw("mip_count", &VTF::getMipCount, &VTF::setMipCount) .def("set_recommended_mip_count", &VTF::setRecommendedMipCount) .def("compute_mips", &VTF::computeMips, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def_property("frame_count", &VTF::getFrameCount, &VTF::setFrameCount) - .def_property_readonly("face_count", &VTF::getFaceCount) + .def_prop_rw("frame_count", &VTF::getFrameCount, &VTF::setFrameCount) + .def_prop_ro("face_count", &VTF::getFaceCount) .def("set_face_count", &VTF::setFaceCount, py::arg("is_cubemap"), py::arg("has_spheremap") = false) - .def_property("slice_count", &VTF::getSliceCount, &VTF::setSliceCount) + .def_prop_rw("slice_count", &VTF::getSliceCount, &VTF::setSliceCount) .def("set_frame_face_and_slice_count", &VTF::setFrameFaceAndSliceCount, py::arg("new_frame_count"), py::arg("is_cubemap"), py::arg("has_spheremap") = false, py::arg("new_slice_count") = 1) - .def_property("start_frame", &VTF::getStartFrame, &VTF::setStartFrame) - .def_property("reflectivity", &VTF::getReflectivity, &VTF::setReflectivity) + .def_prop_rw("start_frame", &VTF::getStartFrame, &VTF::setStartFrame) + .def_prop_rw("reflectivity", &VTF::getReflectivity, &VTF::setReflectivity) .def("compute_reflectivity", &VTF::computeReflectivity) - .def_property("bumpmap_scale", &VTF::getBumpMapScale, &VTF::setBumpMapScale) - .def_property_readonly("thumbnail_format", &VTF::getThumbnailFormat) - .def_property_readonly("thumbnail_width", &VTF::getThumbnailWidth) - .def_property_readonly("thumbnail_height", &VTF::getThumbnailHeight) + .def_prop_rw("bumpmap_scale", &VTF::getBumpMapScale, &VTF::setBumpMapScale) + .def_prop_ro("thumbnail_format", &VTF::getThumbnailFormat) + .def_prop_ro("thumbnail_width", &VTF::getThumbnailWidth) + .def_prop_ro("thumbnail_height", &VTF::getThumbnailHeight) // Skip getResources // Skip getResource - .def("set_particle_sheet_resource", [](VTF& self, const py::bytes& value) { - const std::string_view valueView{value}; - return self.setParticleSheetResource({reinterpret_cast(valueView.data()), valueView.size()}); - }, py::arg("value")) + .def("set_particle_sheet_resource", [](VTF& self, const py::bytes& value) { return self.setParticleSheetResource({reinterpret_cast(value.data()), value.size()}); }, py::arg("value")) .def("remove_particle_sheet_resource", &VTF::removeParticleSheetResource) .def("set_crc_resource", &VTF::setCRCResource, py::arg("value")) .def("remove_crc_resource", &VTF::removeCRCResource) @@ -393,59 +378,57 @@ void register_python(py::module_& m) { .def("remove_extended_flags_resource", &VTF::removeExtendedFlagsResource) .def("set_keyvalues_data_resource", &VTF::setKeyValuesDataResource, py::arg("value")) .def("remove_keyvalues_data_resource", &VTF::removeKeyValuesDataResource) - .def_property("compression_level", &VTF::getCompressionLevel, &VTF::setCompressionLevel) - .def_property("compression_method", &VTF::getCompressionMethod, &VTF::setCompressionMethod) + .def_prop_rw("compression_level", &VTF::getCompressionLevel, &VTF::setCompressionLevel) + .def_prop_rw("compression_method", &VTF::getCompressionMethod, &VTF::setCompressionMethod) .def("has_image_data", &VTF::hasImageData) .def("image_data_is_srgb", &VTF::imageDataIsSRGB) - .def("get_image_data_raw", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) -> py::bytes { + .def("get_image_data_raw", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { const auto d = self.getImageDataRaw(mip, frame, face, slice); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("get_image_data_as", [](const VTF& self, ImageFormat newFormat, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) -> py::bytes { + .def("get_image_data_as", [](const VTF& self, ImageFormat newFormat, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { const auto d = self.getImageDataAs(newFormat, mip, frame, face, slice); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }, py::arg("new_format"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("get_image_data_as_rgba8888", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) -> py::bytes { + .def("get_image_data_as_rgba8888", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { const auto d = self.getImageDataAsRGBA8888(mip, frame, face, slice); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) .def("set_image", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { - const std::string_view imageDataView{imageData}; - return self.setImage({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, height, filter, mip, frame, face, slice); + return self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, filter, mip, frame, face, slice); }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("filter"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) .def("set_image_from_file", py::overload_cast(&VTF::setImage), py::arg("image_path"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("save_image", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) -> py::bytes { + .def("save_image", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { const auto d = self.saveImageToFile(mip, frame, face, slice, fileFormat); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) .def("save_image_to_file", py::overload_cast(&VTF::saveImageToFile, py::const_), py::arg("image_path"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) .def("has_thumbnail_data", &VTF::hasThumbnailData) - .def("get_thumbnail_data_raw", [](const VTF& self) -> py::bytes { + .def("get_thumbnail_data_raw", [](const VTF& self) { const auto d = self.getThumbnailDataRaw(); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }) - .def("get_thumbnail_data_as", [](const VTF& self, ImageFormat newFormat) -> py::bytes { + .def("get_thumbnail_data_as", [](const VTF& self, ImageFormat newFormat) { const auto d = self.getThumbnailDataAs(newFormat); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }, py::arg("new_format")) - .def("get_thumbnail_data_as_rgba8888", [](const VTF& self) -> py::bytes { + .def("get_thumbnail_data_as_rgba8888", [](const VTF& self) { const auto d = self.getThumbnailDataAsRGBA8888(); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }) .def("set_thumbnail", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height) { - const std::string_view imageDataView{imageData}; - return self.setThumbnail({reinterpret_cast(imageDataView.data()), imageDataView.size()}, format, width, height); + return self.setThumbnail({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height); }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height")) .def("compute_thumbnail", &VTF::computeThumbnail, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) .def("remove_thumbnail", &VTF::removeThumbnail) - .def("save_thumbnail", [](const VTF& self, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) -> py::bytes { + .def("save_thumbnail", [](const VTF& self, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { const auto d = self.saveThumbnailToFile(fileFormat); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) .def("save_thumbnail_to_file", py::overload_cast(&VTF::saveThumbnailToFile, py::const_), py::arg("image_path"), py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("bake", [](const VTF& self) -> py::bytes { + .def("bake", [](const VTF& self) { const auto d = self.bake(); - return {reinterpret_cast(d.data()), d.size()}; + return py::bytes{d.data(), d.size()}; }) .def("bake_to_file", py::overload_cast(&VTF::bake, py::const_), py::arg("vtf_path")); }