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"));
}