Skip to content

Commit d9040d5

Browse files
committed
[v0.1.16] 2026-03-12
- Updated helios-core to v1.3.67 ## Context - Added primitive texture management methods: `getPrimitiveTextureFile()`, `setPrimitiveTextureFile()`, `getPrimitiveTextureSize()`, `getPrimitiveTextureUV()`, `primitiveTextureHasTransparencyChannel()`, `getPrimitiveSolidFraction()`, `overridePrimitiveTextureColor()`, `usePrimitiveTextureColor()`, `isPrimitiveTextureColorOverridden()` - Primitive getters now accept a list of UUIDs for efficient batch queries returning NumPy arrays (e.g., `getPrimitiveNormal([uuid1, uuid2])` returns an ndarray of shape (N, 3)) - Added `getAll*` convenience methods that query all primitives in the context (e.g., `getAllPrimitiveNormals()`) - Extended `PrimitiveInfo` with `texture_file`, `texture_uv`, and `solid_fraction` fields - Added timeseries data management: `addTimeseriesData()`, `setCurrentTimeseriesPoint()`, `queryTimeseriesData()`, `queryTimeseriesDate()`, `queryTimeseriesTime()`, `getTimeseriesLength()`, `doesTimeseriesVariableExist()`, `listTimeseriesVariables()`, `loadTabularTimeseriesData()` ## Radiation Model - Added EXR image export methods: `writeCameraImageDataEXR()`, `writeDepthImageData()`, `writeDepthImageDataEXR()`, `writeNormDepthImage()` - Added `getBackendName()` and `probeAnyGPUBackend()` for runtime GPU backend detection - Updated error messages to reflect runtime backend auto-detection (OptiX 8 -> OptiX 6 -> Vulkan) ## Validation - Added `isinstance()`-based type validation to PlantArchitecture and RadiationModel methods per argument type validation policy - Added `validate_position_like()`, `validate_direction_like()`, and `validate_size_like()` validators for flexible parameter types
1 parent e9a2b85 commit d9040d5

25 files changed

Lines changed: 3773 additions & 627 deletions

.github/workflows/build-wheels.yml

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@ jobs:
199199
run: python -m cibuildwheel --output-dir wheelhouse
200200
timeout-minutes: 60 # Increased for LiDAR tests which are comprehensive
201201
env:
202-
# Build for Python 3.9+ on all platforms
203-
CIBW_BUILD: cp39-* cp310-* cp311-* cp312-* cp313-*
202+
# Build for Python 3.10+ on all platforms
203+
CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* cp314-*
204204

205205
# Fail fast on build errors instead of continuing to next Python version
206206
CIBW_BUILD_VERBOSITY: 1
@@ -272,7 +272,7 @@ jobs:
272272
yum install -y zlib-devel mesa-libGL-devel mesa-libEGL-devel libX11-devel libXrandr-devel mesa-libGLU-devel libXinerama-devel libXcursor-devel libXi-devel libXxf86vm-devel &&
273273
export PKG_CONFIG_PATH="/usr/lib64/pkgconfig:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" &&
274274
export CMAKE_PREFIX_PATH="/usr:${CMAKE_PREFIX_PATH}" &&
275-
/opt/python/cp39-cp39/bin/python build_scripts/prepare_wheel.py --buildmode release --verbose --clean
275+
/opt/python/cp310-cp310/bin/python build_scripts/prepare_wheel.py --buildmode release --verbose --clean
276276
# Per-ABI: do not rebuild; only stage/copy already built artifacts
277277
CIBW_BEFORE_BUILD_LINUX: >
278278
if [ -f /opt/rh/devtoolset-10/enable ]; then
@@ -290,7 +290,7 @@ jobs:
290290
export PKG_CONFIG_PATH="/usr/lib64/pkgconfig:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" &&
291291
export CMAKE_PREFIX_PATH="/usr:${CMAKE_PREFIX_PATH}" &&
292292
echo "Reusing native build; packaging only." &&
293-
/opt/python/cp39-cp39/bin/python build_scripts/prepare_wheel.py --buildmode release --verbose
293+
/opt/python/cp310-cp310/bin/python build_scripts/prepare_wheel.py --buildmode release --verbose
294294
295295
# Manylinux-specific environment for zlib compatibility and OpenGL discovery
296296
# Use documented $PATH expansion to append devtoolset without overriding cibuildwheel's Python
@@ -701,15 +701,15 @@ jobs:
701701
matrix:
702702
include:
703703
- os: ubuntu-22.04
704-
python-version: '3.9'
704+
python-version: '3.10'
705705
- os: ubuntu-22.04
706-
python-version: '3.13'
706+
python-version: '3.14'
707707
- os: windows-2022
708-
python-version: '3.9'
708+
python-version: '3.10'
709709
- os: windows-2022
710-
python-version: '3.13'
710+
python-version: '3.14'
711711
- os: macos-14
712-
python-version: '3.13'
712+
python-version: '3.14'
713713

714714
steps:
715715
- name: Checkout PyHelios for tests
@@ -1299,8 +1299,18 @@ jobs:
12991299
merge-multiple: true
13001300
path: wheelhouse
13011301

1302+
- name: List wheel sizes
1303+
run: |
1304+
echo "=== Wheel sizes (PyPI limit: 100MB default) ==="
1305+
ls -lhS wheelhouse/
1306+
echo ""
1307+
echo "=== Wheels exceeding 100MB ==="
1308+
find wheelhouse/ -name "*.whl" -size +100M -exec ls -lh {} \; || echo "None found"
1309+
13021310
- name: Publish to PyPI
13031311
uses: pypa/gh-action-pypi-publish@release/v1
13041312
with:
13051313
packages-dir: wheelhouse/
13061314
verify-metadata: false # Skip metadata verification due to dynamic versioning
1315+
skip-existing: true # Allow re-runs without failing on already-uploaded wheels
1316+
verbose: true # Show full PyPI error details on upload failure

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ See the Helios C++ documentation for a more in-depth description of Helios: http
2525

2626
### Installation
2727

28+
**Requirements:** Python 3.10 or later (pre-built wheels available for Python 3.10–3.14)
29+
2830
**Easy Install (Recommended):**
2931
```bash
3032
pip install pyhelios3d
@@ -50,7 +52,7 @@ If you need to customize plugins or build from source:
5052

5153
**Prerequisites:**
5254
- Visual Studio 2019+ or Build Tools for Visual Studio
53-
- Python 3.7+
55+
- Python 3.10+
5456

5557
```bash
5658
# Clone repository
@@ -68,7 +70,7 @@ pip install -e .
6870

6971
**Prerequisites:**
7072
- Xcode command line tools
71-
- Python 3.7+
73+
- Python 3.10+
7274

7375
```bash
7476
# Install Xcode command line tools
@@ -91,7 +93,7 @@ pip install -e .
9193
**Prerequisites:**
9294
- Build essentials
9395
- CMake
94-
- Python 3.7+
96+
- Python 3.10+
9597

9698
```bash
9799
# Clone repository
@@ -121,9 +123,11 @@ PyHelios plugins have three types of GPU support:
121123
**CPU-Only Plugins** (no GPU needed):
122124
- All other plugins (PlantArchitecture, Photosynthesis, SolarPosition, etc.)
123125

124-
**For GPU Acceleration** (optional), ensure you have:
125-
- Vulkan SDK installed (supports NVIDIA, AMD, Intel, Apple Silicon GPUs)
126-
- OR for NVIDIA-optimized path: CUDA Toolkit (version 11.8 or 12.x) + OptiX
126+
**For GPU Acceleration** (optional), ensure you have at least one:
127+
- Vulkan loader library (macOS/Linux only; no extra packages on Windows)
128+
- OR for NVIDIA-optimized path: CUDA 12.0+ with driver >= 560 (OptiX 8.1) or CUDA 9.0+ with driver < 560 (OptiX 6.5)
129+
130+
The radiation plugin auto-detects the best available backend at runtime (OptiX 8 -> OptiX 6 -> Vulkan).
127131

128132
**Verification:**
129133
```bash

build_scripts/build_helios.py

Lines changed: 3 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -780,13 +780,8 @@ def run_cmake_configure(self, additional_args: Optional[List[str]] = None) -> No
780780
'-DBUILD_SHARED_LIBS=ON', # Build as shared library on Unix
781781
])
782782

783-
# Windows-specific workaround for zlib resource compilation issue
783+
# Windows-specific: disable resource compilation if CMAKE_RC_COMPILER is not set
784784
if self.platform_name == 'Windows':
785-
# The RC compiler fails on win32/zlib1.rc due to C syntax in included headers
786-
# Patch zlib CMakeLists.txt to disable the problematic shared library target
787-
self._patch_zlib_cmake_for_windows()
788-
789-
# Disable resource compilation entirely if CMAKE_RC_COMPILER is not set
790785
if not os.environ.get('CMAKE_RC_COMPILER'):
791786
cmake_cmd.extend(['-DCMAKE_RC_COMPILER=']) # Empty RC compiler disables resource compilation
792787

@@ -1477,8 +1472,8 @@ def copy_radiation_shader_files(self) -> None:
14771472
"The radiation plugin requires GPU backend shader files.\n"
14781473
"Shader files (SPIR-V for Vulkan, PTX for OptiX) should be generated during CMake build.\n\n"
14791474
"To fix this issue:\n"
1480-
"1. Ensure Vulkan SDK is installed (primary cross-platform backend)\n"
1481-
"2. Or ensure CUDA toolkit + OptiX are installed (NVIDIA-only backend)\n"
1475+
"1. Ensure Vulkan loader is installed (macOS/Linux; bundled on Windows)\n"
1476+
"2. Or ensure CUDA toolkit is installed (NVIDIA GPU backend)\n"
14821477
"3. Check CMake configuration enables at least one GPU backend\n"
14831478
"4. Run build with --clean to rebuild from scratch\n\n"
14841479
f"Expected shader directory: {shader_source_dir}\n"
@@ -1572,70 +1567,6 @@ def build(self, cmake_args: Optional[List[str]] = None) -> Path:
15721567
print(f"Built with plugins: {final_plugins}")
15731568
return output_library
15741569

1575-
def _patch_zlib_cmake_for_windows(self) -> None:
1576-
"""
1577-
Patch zlib's CMakeLists.txt on Windows to disable the shared library target
1578-
that causes resource compilation errors with win32/zlib1.rc.
1579-
"""
1580-
zlib_cmake_path = self.helios_root / "core" / "lib" / "zlib" / "CMakeLists.txt"
1581-
1582-
if not zlib_cmake_path.exists():
1583-
print("Warning: zlib CMakeLists.txt not found, skipping patch")
1584-
return
1585-
1586-
print("Patching zlib CMakeLists.txt to disable shared library on Windows...")
1587-
1588-
try:
1589-
# Read the original file
1590-
with open(zlib_cmake_path, 'r') as f:
1591-
content = f.read()
1592-
1593-
# Check if already patched (to avoid double-patching)
1594-
if "# PYHELIOS PATCH: Disabled shared library" in content:
1595-
print("zlib CMakeLists.txt already patched")
1596-
return
1597-
except Exception as e:
1598-
print(f"Warning: Failed to read zlib CMakeLists.txt: {e}")
1599-
return
1600-
1601-
# Patch: comment out the shared library creation and related lines
1602-
patches = [
1603-
# Comment out the shared library creation
1604-
("add_library(zlib SHARED", "# PYHELIOS PATCH: Disabled shared library\n# add_library(zlib SHARED"),
1605-
# Comment out the include directories for shared lib
1606-
("target_include_directories(zlib PUBLIC", "# target_include_directories(zlib PUBLIC"),
1607-
# Comment out all properties for shared lib
1608-
("set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL)", "# set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL)"),
1609-
("set_target_properties(zlib PROPERTIES SOVERSION 1)", "# set_target_properties(zlib PROPERTIES SOVERSION 1)"),
1610-
(" set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION})", " # set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION})"),
1611-
(" set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z)", " set_target_properties(zlibstatic PROPERTIES OUTPUT_NAME z)"),
1612-
(" set_target_properties(zlib PROPERTIES LINK_FLAGS", " # set_target_properties(zlib PROPERTIES LINK_FLAGS"),
1613-
(" set_target_properties(zlib PROPERTIES SUFFIX \"1.dll\")", " # set_target_properties(zlib PROPERTIES SUFFIX \"1.dll\")"),
1614-
# Update install targets to exclude zlib shared library
1615-
(" install(TARGETS zlib zlibstatic", " install(TARGETS zlibstatic"),
1616-
# Add static runtime linking for zlib
1617-
("add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})",
1618-
"add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})\n# PYHELIOS PATCH: Use static MSVC runtime\nif(MSVC)\n set_target_properties(zlibstatic PROPERTIES MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\nendif()"),
1619-
# Disable problematic resource compilation
1620-
("if(NOT MINGW)\n set(ZLIB_DLL_SRCS\n win32/zlib1.rc # If present will override custom build rule below.\n )\nendif()",
1621-
"# PYHELIOS PATCH: Disable resource compilation to avoid RC errors\n# if(NOT MINGW)\n# set(ZLIB_DLL_SRCS\n# win32/zlib1.rc # If present will override custom build rule below.\n# )\n# endif()"),
1622-
]
1623-
1624-
for old, new in patches:
1625-
if old in content:
1626-
content = content.replace(old, new)
1627-
print(f" Patched: {old[:50]}...")
1628-
1629-
# Write the patched file back
1630-
try:
1631-
with open(zlib_cmake_path, 'w') as f:
1632-
f.write(content)
1633-
print("zlib CMakeLists.txt patched successfully")
1634-
except Exception as e:
1635-
print(f"Warning: Failed to write patched zlib CMakeLists.txt: {e}")
1636-
raise HeliosBuildError(f"Could not patch zlib CMakeLists.txt: {e}")
1637-
1638-
16391570
def get_default_plugins() -> List[str]:
16401571
"""
16411572
Get the default set of plugins (only the currently integrated plugins).

docs/CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Changelog
22

3+
# [v0.1.16] 2026-03-12
4+
5+
- Updated helios-core to v1.3.67
6+
7+
## Context
8+
- Added primitive texture management methods: `getPrimitiveTextureFile()`, `setPrimitiveTextureFile()`, `getPrimitiveTextureSize()`, `getPrimitiveTextureUV()`, `primitiveTextureHasTransparencyChannel()`, `getPrimitiveSolidFraction()`, `overridePrimitiveTextureColor()`, `usePrimitiveTextureColor()`, `isPrimitiveTextureColorOverridden()`
9+
- Primitive getters now accept a list of UUIDs for efficient batch queries returning NumPy arrays (e.g., `getPrimitiveNormal([uuid1, uuid2])` returns an ndarray of shape (N, 3))
10+
- Added `getAll*` convenience methods that query all primitives in the context (e.g., `getAllPrimitiveNormals()`)
11+
- Extended `PrimitiveInfo` with `texture_file`, `texture_uv`, and `solid_fraction` fields
12+
- Added timeseries data management: `addTimeseriesData()`, `setCurrentTimeseriesPoint()`, `queryTimeseriesData()`, `queryTimeseriesDate()`, `queryTimeseriesTime()`, `getTimeseriesLength()`, `doesTimeseriesVariableExist()`, `listTimeseriesVariables()`, `loadTabularTimeseriesData()`
13+
14+
## Radiation Model
15+
- Added EXR image export methods: `writeCameraImageDataEXR()`, `writeDepthImageData()`, `writeDepthImageDataEXR()`, `writeNormDepthImage()`
16+
- Added `getBackendName()` and `probeAnyGPUBackend()` for runtime GPU backend detection
17+
- Updated error messages to reflect runtime backend auto-detection (OptiX 8 -> OptiX 6 -> Vulkan)
18+
19+
## Validation
20+
- Added `isinstance()`-based type validation to PlantArchitecture and RadiationModel methods per argument type validation policy
21+
- Added `validate_position_like()`, `validate_direction_like()`, and `validate_size_like()` validators for flexible parameter types
22+
323
# [v0.1.15] 2026-03-06
424

525
- Updated helios-core to v1.3.65

docs/cross_platform.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
PyHelios is designed to work seamlessly across Windows, macOS, and Linux. This guide covers platform-specific considerations and features.
44

5+
## Python Version Support
6+
7+
PyHelios requires **Python 3.10 or later**. Pre-built wheels are available for Python 3.10–3.14.
8+
9+
| Python Version | Status |
10+
|----------------|--------|
11+
| 3.10 – 3.14 | Supported (pre-built wheels available) |
12+
| 3.9 and earlier | Not supported |
13+
514
## Platform Support Matrix
615

716
| Feature | Windows | macOS | Linux | Notes |

docs/plugin_radiation.md

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,42 @@ The radiation plugin supports two GPU backends. At least one must be available:
1212

1313
### Vulkan Backend (All Platforms)
1414

15-
Supports NVIDIA, AMD, Intel, and Apple Silicon GPUs via Vulkan compute shaders with software BVH traversal.
15+
Supports NVIDIA, AMD, Intel, and Apple Silicon GPUs via Vulkan compute shaders with software BVH traversal. Vulkan headers and the glslang shader compiler are bundled with Helios, so no external Vulkan SDK installation is required on Windows.
1616

1717
| Requirement | macOS | Linux | Windows |
1818
|-------------|-------|-------|---------|
1919
| **GPU** | Any with Vulkan support | Any with Vulkan support | Any with Vulkan support |
20-
| **Vulkan SDK** | Via MoltenVK (bundled with macOS) | <a href="https://vulkan.lunarg.com/sdk/home">Vulkan SDK</a> | <a href="https://vulkan.lunarg.com/sdk/home">Vulkan SDK</a> |
20+
| **Dependencies** | `brew install vulkan-loader molten-vk` | `sudo apt-get install libvulkan-dev` | None (all bundled) |
2121

2222
### OptiX Backend (NVIDIA Only)
2323

24-
Provides optimized ray tracing on NVIDIA GPUs using hardware RT cores.
24+
Provides optimized ray tracing on NVIDIA GPUs using hardware RT cores. Two OptiX versions are supported:
2525

26-
| Requirement | macOS | Linux | Windows |
27-
|-------------|-------|-------|---------|
28-
| **GPU** | Not supported | CUDA-capable (compute capability 3.5+) | CUDA-capable (compute capability 3.5+) |
29-
| **CUDA Runtime** | Not supported | Version 9.0+ | Version 9.0+ |
30-
| **OptiX Runtime** | Not supported | Bundled with PyHelios | Bundled with PyHelios |
26+
| Backend | Driver Requirement | CUDA | Notes |
27+
|---------|-------------------|------|-------|
28+
| **OptiX 8.1** | NVIDIA driver >= 560 | CUDA 12.0+ | Default for modern drivers |
29+
| **OptiX 6.5** | NVIDIA driver < 560 | CUDA 9.0+ | Legacy driver support |
3130

3231
### Backend Selection
3332

34-
Backend selection is automatic based on available hardware. Use the CMake option `FORCE_VULKAN_BACKEND=ON` to force the Vulkan backend for testing.
33+
Backend selection is **automatic at runtime** via GPU hardware probing. When the radiation model starts, it probes compiled-in backends in priority order (OptiX 8 -> OptiX 6 -> Vulkan) and selects the first one that is compatible with the current hardware. If no compatible GPU is found, a clear diagnostic error is raised.
34+
35+
You can query the active backend at runtime:
36+
37+
```python
38+
with RadiationModel(context) as radiation:
39+
print(f"Active backend: {radiation.getBackendName()}")
40+
# e.g., "OptiX 8.1", "OptiX 6.5", "Vulkan Compute"
41+
```
42+
43+
You can also probe GPU availability without creating a full radiation model:
44+
45+
```python
46+
if RadiationModel.probeAnyGPUBackend():
47+
print("GPU backend available")
48+
else:
49+
print("No compatible GPU backend found")
50+
```
3551

3652
**For CUDA/OptiX setup**, see the comprehensive \ref CUDASetup "CUDA Setup Guide", which covers:
3753
- Choosing the correct CUDA toolkit version: \ref ChoosingCUDA
@@ -1059,6 +1075,48 @@ radiation.writeCameraImageData(
10591075
imagefile_base="raw_data",
10601076
image_path="./data"
10611077
)
1078+
1079+
# Camera image data in EXR format (lossless float precision, v1.3.66+)
1080+
radiation.writeCameraImageDataEXR(
1081+
camera="overhead_camera",
1082+
band="PAR", # Single band
1083+
imagefile_base="raw_float_data",
1084+
image_path="./data"
1085+
)
1086+
1087+
# Multi-band EXR (all bands as separate channels in one file)
1088+
radiation.writeCameraImageDataEXR(
1089+
camera="overhead_camera",
1090+
band=["Red", "Green", "Blue"], # Multiple bands
1091+
imagefile_base="rgb_float_data",
1092+
image_path="./data"
1093+
)
1094+
```
1095+
1096+
### Depth Image Export
1097+
1098+
```python
1099+
# Depth image as ASCII text file
1100+
radiation.writeDepthImageData(
1101+
camera_label="overhead_camera",
1102+
imagefile_base="depth_data",
1103+
image_path="./data"
1104+
)
1105+
1106+
# Depth image as EXR file (lossless float precision, v1.3.66+)
1107+
radiation.writeDepthImageDataEXR(
1108+
camera_label="overhead_camera",
1109+
imagefile_base="depth_float",
1110+
image_path="./data"
1111+
)
1112+
1113+
# Normalized depth image (grayscale JPEG for visualization)
1114+
radiation.writeNormDepthImage(
1115+
camera_label="overhead_camera",
1116+
imagefile_base="depth_visual",
1117+
max_depth=50.0, # Maximum depth for normalization
1118+
image_path="./data"
1119+
)
10621120
```
10631121

10641122
### Object Detection Training Data
@@ -1314,15 +1372,24 @@ except RuntimeError as e:
13141372
from pyhelios import RadiationModel
13151373
from pyhelios.exceptions import HeliosGPUInitializationError
13161374

1375+
# Check GPU availability before creating RadiationModel
1376+
if not RadiationModel.probeAnyGPUBackend():
1377+
print("No compatible GPU backend found")
1378+
print("Supported backends: OptiX 8 (NVIDIA driver >= 560), OptiX 6 (NVIDIA driver < 560), Vulkan")
1379+
13171380
try:
13181381
radiation = RadiationModel(context)
1319-
1382+
print(f"Using backend: {radiation.getBackendName()}")
1383+
13201384
except Exception as e:
13211385
print(f"RadiationModel initialization failed: {e}")
1322-
1323-
# Check if GPU is available
1386+
1387+
# Runtime backend probing provides clear diagnostic messages
13241388
if "Vulkan" in str(e):
1325-
print("Vulkan backend error - check Vulkan SDK installation and GPU drivers")
1389+
print("Vulkan backend error - check GPU drivers")
1390+
print(" macOS: brew install vulkan-loader molten-vk")
1391+
print(" Linux: sudo apt-get install libvulkan-dev")
1392+
print(" Windows: No additional packages needed")
13261393
elif "OptiX" in str(e):
13271394
print("OptiX backend error - check CUDA/OptiX installation and NVIDIA drivers")
13281395
elif "GPU" in str(e):

0 commit comments

Comments
 (0)