diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 13d20fd..5392c0b 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,29 +1,86 @@ -# This workflows will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries +# This workflow uploads a Python Package to PyPI when a GitHub release is created +# It works together with the copybara workflow that creates the release after code sync name: Upload Python Package + on: release: - types: [created] + types: [created, published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g., v2024.12.1)' + required: true + type: string + test_pypi: + description: 'Publish to Test PyPI instead of production' + required: false + type: boolean + default: false + jobs: deploy: runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/zutil permissions: - id-token: write + id-token: write # Required for trusted publishing + steps: - - uses: actions/checkout@v5 + - name: Checkout code + uses: actions/checkout@v5 + with: + ref: ${{ github.event.release.tag_name || inputs.tag }} + - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.x" + + - name: Extract version from tag + id: get_version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ inputs.tag }}" + TEST_PYPI="${{ inputs.test_pypi }}" + else + TAG="${{ github.event.release.tag_name }}" + # Check if this is a test release (draft or prerelease) + if [ "${{ github.event.release.draft }}" = "true" ] || [ "${{ github.event.release.prerelease }}" = "true" ]; then + TEST_PYPI="true" + else + TEST_PYPI="false" + fi + fi + echo "tag_name=$TAG" >> $GITHUB_OUTPUT + echo "test_pypi=$TEST_PYPI" >> $GITHUB_OUTPUT + echo "Building version from tag: $TAG (Test PyPI: $TEST_PYPI)" + - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel get-version --upgrade - - name: Build and publish + + - name: Build package env: - RELEASE_VERSION: ${{ github.event.release.tag_name }} + RELEASE_VERSION: ${{ steps.get_version.outputs.tag_name }} run: | + echo "๐Ÿ“ฆ Building package for release: $RELEASE_VERSION" python setup.py sdist bdist_wheel + + - name: Verify package + run: | + echo "๐Ÿ” Checking built package" + pip install twine + python -m twine check dist/* + + # Show package contents + echo "๐Ÿ“‹ Package contents:" + ls -la dist/ + - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + repository-url: ${{ steps.get_version.outputs.test_pypi == 'true' && 'https://test.pypi.org/legacy/' || '' }} diff --git a/.github/workflows/zutil-pypi-release.yml b/.github/workflows/zutil-pypi-release.yml new file mode 100644 index 0000000..ba948e7 --- /dev/null +++ b/.github/workflows/zutil-pypi-release.yml @@ -0,0 +1,131 @@ +name: Create zUtil Release + +on: + pull_request: + branches: [main] + types: [closed] + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g., v2024.12.1)' + required: true + type: string + test_mode: + description: 'Test mode - creates draft release and test tag' + required: false + type: boolean + default: false + pypi_test: + description: 'Publish to Test PyPI instead of production' + required: false + type: boolean + default: false + +jobs: + create-release: + name: Create GitHub Release + runs-on: ubuntu-latest + if: | + (github.event.pull_request.merged == true && + startsWith(github.head_ref, 'release/v')) || + github.event_name == 'workflow_dispatch' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + + - name: Get release tag + id: get_tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ inputs.tag }}" + if [ "${{ inputs.test_mode }}" = "true" ]; then + # Add test suffix for test mode + TAG="${TAG}-test" + fi + else + # Extract tag from PR branch name (e.g., release/v2024.12.1 -> v2024.12.1) + TAG="${{ github.head_ref }}" + TAG="${TAG#release/}" + fi + echo "tag_name=$TAG" >> $GITHUB_OUTPUT + echo "version=${TAG#v}" >> $GITHUB_OUTPUT + echo "test_mode=${{ inputs.test_mode || 'false' }}" >> $GITHUB_OUTPUT + echo "pypi_test=${{ inputs.pypi_test || 'false' }}" >> $GITHUB_OUTPUT + echo "Processing release: $TAG" + + - name: Verify version consistency + run: | + VERSION="${{ steps.get_tag.outputs.version }}" + echo "Expected version: $VERSION" + + # The setup.py uses RELEASE_VERSION environment variable, so we just need to verify + # that the code was properly synced. We can check for any version files if they exist. + echo "โœ… Version will be set via RELEASE_VERSION environment variable during build" + + - name: Create and push Git tag + if: steps.get_tag.outputs.test_mode != 'true' + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + # Check if tag already exists + if git rev-parse "${{ steps.get_tag.outputs.tag_name }}" >/dev/null 2>&1; then + echo "Tag ${{ steps.get_tag.outputs.tag_name }} already exists, skipping tag creation" + else + git tag ${{ steps.get_tag.outputs.tag_name }} + git push origin ${{ steps.get_tag.outputs.tag_name }} + echo "Created and pushed tag: ${{ steps.get_tag.outputs.tag_name }}" + fi + + - name: Create test Git tag + if: steps.get_tag.outputs.test_mode == 'true' + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + echo "๐Ÿงช Test mode: Creating temporary test tag" + # Force create/update the test tag (will be cleaned up) + git tag -f ${{ steps.get_tag.outputs.tag_name }} + git push -f origin ${{ steps.get_tag.outputs.tag_name }} + echo "Created test tag: ${{ steps.get_tag.outputs.tag_name }}" + + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.get_tag.outputs.tag_name }} + release_name: zUtil ${{ steps.get_tag.outputs.tag_name }}${{ steps.get_tag.outputs.test_mode == 'true' && ' (TEST)' || '' }} + body: | + ## zUtil Release ${{ steps.get_tag.outputs.tag_name }}${{ steps.get_tag.outputs.test_mode == 'true' && ' (TEST)' || '' }} + + ${{ steps.get_tag.outputs.test_mode == 'true' && '๐Ÿงช **This is a TEST release for validation purposes**' || '๐ŸŽ‰ This release will be automatically published to PyPI!' }} + + ### Install this version: + ```bash + ${{ steps.get_tag.outputs.pypi_test == 'true' && 'pip install --index-url https://test.pypi.org/simple/ zutil==' || 'pip install zutil==' }}${{ steps.get_tag.outputs.version }} + ``` + + ### Changes: + This release was automatically synced from the zCFD repository. + + ${{ steps.get_tag.outputs.test_mode == 'true' && 'The PyPI package will be published to Test PyPI for validation.' || 'The PyPI package will be built and published automatically by the existing python-publish.yml workflow.' }} + draft: ${{ steps.get_tag.outputs.test_mode == 'true' }} + prerelease: ${{ steps.get_tag.outputs.test_mode == 'true' }} + + - name: Cleanup test tag + if: steps.get_tag.outputs.test_mode == 'true' + run: | + echo "๐Ÿงน Test mode: Cleaning up test tag after 5 minutes" + sleep 300 # Wait 5 minutes for testing + git push origin --delete ${{ steps.get_tag.outputs.tag_name }} || echo "Test tag already cleaned up" + + - name: Cleanup release branch + if: github.event.pull_request.merged == true + run: | + # Delete the release branch after successful merge and release creation + BRANCH_NAME="${{ github.head_ref }}" + git push origin --delete "$BRANCH_NAME" || echo "Branch already deleted" + echo "๐Ÿงน Cleaned up release branch: $BRANCH_NAME" \ No newline at end of file diff --git a/README.md b/README.md index 0b716d9..c32b40a 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,241 @@ [![PyPI version fury.io](https://badge.fury.io/py/zutil.svg)](https://pypi.python.org/pypi/zutil/) -Note: This library sets Paraview compatibility to version >= 5.4 +**Python utilities for zCFD users** + +zutil is a comprehensive Python toolkit designed to work with zCFD CFD simulation results and provide powerful pre- and post-processing capabilities. This library offers streamlined workflows for data analysis, visualization, wind energy modeling, acoustic analysis, and geometric transformations. + +## Installation + +Install zutil from PyPI: + +```bash +pip install zutil +``` + +**Note:** This library is compatible with Paraview 6.0+ + +## Package Structure + +zutil is organized into several specialized modules: + +- **[plot](#plotting)** - Advanced plotting and visualization tools for zCFD results +- **[post](#post-processing)** - Post-processing utilities using Paraview integration +- **[fileutils](#file-utilities)** - File I/O operations and result data access +- **[analysis](#analysis-tools)** - Analysis tools including acoustic analysis +- **[transform](#geometric-transformations)** - Geometric transformation utilities +- **[zWind](#wind-energy-modeling)** - Wind energy modeling and turbine analysis + +## Quick Start + +### Loading zCFD Results + +The fundamental function for accessing zCFD simulation data: + +```python +from zutil.fileutils import get_zcfd_result + +# Load a zCFD case result +result = get_zcfd_result('my_case.py') + +# Access case parameters +params = result.parameters + +# Get report data +report = result.report +print(report.header_list) # Available data columns +``` + +### Plotting Report Files + +Create professional plots from zCFD report files: + +```python +from zutil.plot import Report +import matplotlib.pyplot as plt + +# Load and plot residuals +report = Report('my_case.py') +report.plot_residuals() +plt.show() + +# Plot specific variables +report.plot_report_array('Cd') +plt.show() +``` + +## Module Documentation + +### Plotting + +The `zutil.plot` module provides comprehensive plotting capabilities: + +```python +from zutil.plot import Report, zCFD_Plots + +# Individual case plotting +report = Report('case.py') +report.plot_residuals() +report.plot_forces() +report.plot_linear_solve_performance() + +# Multi-case plotting (handles overset cases automatically) +plots = zCFD_Plots('case.py') +plots.plot_residuals() +plots.plot_forces() +``` + +**Key Features:** + +- Residual convergence plots +- Force and moment coefficient plotting +- Linear solver performance analysis +- Acoustic analysis visualization +- Professional styling with zCFD branding + +### Post-Processing + +The `zutil.post` module integrates with Paraview for advanced post-processing: + +**Note:** This module requires `pvpython` to be installed. The easiest way to get this is to use a distribution of zCFD. + +```python +from zutil import post + +# Access case parameters +params = post.get_case_parameters('case.py') + +# Calculate forces on walls +force = post.calc_force_wall('wall_name', 'case.py') + +# Extract surface pressure profiles +cp_data = post.cp_profile_wall_from_file('wall_name', 'case.py') + +# Generate Paraview visualizations with zCFD branding +post.vtk_logo_stamp() +``` + +**Key Features:** + +- Force and moment calculations +- Surface pressure and friction analysis +- Paraview visualization utilities +- Progress tracking for batch operations + +### File Utilities + +The `zutil.fileutils` module handles all file I/O operations: + +```python +from zutil.fileutils import get_zcfd_result, zCFD_Result + +# Load case data +result = get_zcfd_result('case.py') + +# Access report data directly +report_data = result.report.get_array('Cd') + +# Get case status +success = result.report.get_case_success() +``` + +**Key Features:** + +- Seamless access to zCFD case data +- Report file parsing and analysis +- Parameter extraction from control files +- CSV data handling utilities + +### Analysis Tools + +The `zutil.analysis` module includes acoustic and other analysis capabilities: + +```python +from zutil.analysis.acoustic import calculate_PSD +from zutil.plot import plot_PSD, plot_thirdoctave + +# Perform acoustic analysis +psd_data = calculate_PSD(time_series_data, sample_rate) + +# Create acoustic plots +plot_PSD(psd_data, frequencies) +plot_thirdoctave(psd_data, frequencies) +``` + +**Key Features:** + +- Power spectral density (PSD) analysis +- Third-octave band analysis +- Acoustic visualization tools +- Signal processing utilities + +### Geometric Transformations + +The `zutil.transform` module provides coordinate and geometric operations: + +```python +from zutil.transform import transform +from zutil import vector_from_angle, rotate_vector + +# Coordinate transformations +transformed_points = transform.apply_rotation(points, rotation_matrix) + +# Vector operations +wind_vector = vector_from_angle(wind_direction, wind_speed) +rotated_vector = rotate_vector(vector, axis, angle) +``` + +**Key Features:** + +- Coordinate system transformations +- Vector mathematics utilities +- Rotation and translation operations +- Unit conversions + +### Wind Energy Modeling + +The `zutil.zWind` module provides specialized wind energy tools: + +```python +from zutil.zWind import zTurbine + +# Define wind turbine properties +turbine = zTurbine(name='NREL_5MW', centre=[0, 0, 90]) +``` + +**Key Features:** + +- Wind turbine modeling (zTurbine) +- Actuator disc implementations (zActuatorDisc) +- Blade element momentum theory (zBem) +- Turbine controllers (zController) + +## Requirements + +- Python 3.9+ +- NumPy +- Pandas +- Matplotlib +- IPython/Jupyter support +- PyYAML +- Paraview (for post-processing features) + +## Documentation + +For comprehensive documentation, tutorials, and examples: + +- **Full Documentation:** [https://docs.zenotech.com/](https://docs.zenotech.com/) +- **zCFD Solver:** Download a free trial at [https://zcfd.zenotech.com](https://zcfd.zenotech.com) + +## Support + +- **Issues:** Report bugs and feature requests via the [Issues tab](https://github.com/zenotech/zCFD/issues) +- **Email:** For technical support contact [admin@zenotech.com](mailto:admin@zenotech.com) + +## License + +zutil is released under the MIT License. See LICENSE file for details. + +--- + +_Developed by [Zenotech Ltd](https://zenotech.com)_ diff --git a/setup.cfg b/setup.cfg index 24b0590..c34c7eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = zutil description = "Python utilities for zCFD users" author = "Zenotech" author_email = "support@zenotech.com" -Home-page = https://github.com/zCFD/zutil +home_page = https://github.com/zCFD/zutil long_description = file: README.md long_description_content_type = text/markdown license = MIT @@ -40,10 +40,11 @@ mpi = mpi4py [options.package_data] zutil = - assets/ZCFD_Mark_CMYK_No_Strapline_trans.png - assets/ZCFD_Mark_CMYK.eps - assets/ZCFD_Mark_CMYK.pdf assets/ZCFD_Mark_CMYK.png + assets/ZCFD_NO_STRAPLINE_CMYK.png + assets/ZCFD_NO_STRAPLINE_WHITE.png + assets/ZCFD_STRAPLINE_CMYK.png + assets/ZCFD_STRAPLINE_WHITE.png diff --git a/zutil/assets/ZCFD_Mark_CMYK.eps b/zutil/assets/ZCFD_Mark_CMYK.eps deleted file mode 100644 index 5655575..0000000 Binary files a/zutil/assets/ZCFD_Mark_CMYK.eps and /dev/null differ diff --git a/zutil/assets/ZCFD_Mark_CMYK.pdf b/zutil/assets/ZCFD_Mark_CMYK.pdf deleted file mode 100644 index a9ffb1e..0000000 Binary files a/zutil/assets/ZCFD_Mark_CMYK.pdf and /dev/null differ diff --git a/zutil/assets/ZCFD_Mark_CMYK.png b/zutil/assets/ZCFD_Mark_CMYK.png old mode 100644 new mode 100755 diff --git a/zutil/assets/ZCFD_Mark_CMYK_No_Strapline_trans.png b/zutil/assets/ZCFD_NO_STRAPLINE_CMYK.png old mode 100644 new mode 100755 similarity index 100% rename from zutil/assets/ZCFD_Mark_CMYK_No_Strapline_trans.png rename to zutil/assets/ZCFD_NO_STRAPLINE_CMYK.png diff --git a/zutil/assets/ZCFD_NO_STRAPLINE_WHITE.png b/zutil/assets/ZCFD_NO_STRAPLINE_WHITE.png new file mode 100644 index 0000000..6323def Binary files /dev/null and b/zutil/assets/ZCFD_NO_STRAPLINE_WHITE.png differ diff --git a/zutil/assets/ZCFD_STRAPLINE_CMYK.png b/zutil/assets/ZCFD_STRAPLINE_CMYK.png new file mode 100755 index 0000000..3819eab Binary files /dev/null and b/zutil/assets/ZCFD_STRAPLINE_CMYK.png differ diff --git a/zutil/assets/ZCFD_STRAPLINE_WHITE.png b/zutil/assets/ZCFD_STRAPLINE_WHITE.png new file mode 100644 index 0000000..952b2d4 Binary files /dev/null and b/zutil/assets/ZCFD_STRAPLINE_WHITE.png differ diff --git a/zutil/fileutils.py b/zutil/fileutils.py index 8e99d18..cf5c988 100644 --- a/zutil/fileutils.py +++ b/zutil/fileutils.py @@ -733,11 +733,17 @@ def get_fw_csv_data( return data -def _get_logo_path(strapline=False): +def _get_logo_path(strapline=False, invert=False): + logo = "ZCFD" if strapline: - logo = "ZCFD_Mark_CMYK.png" + logo += "_STRAPLINE" else: - logo = "ZCFD_Mark_CMYK_No_Strapline_trans.png" + logo += "_NO_STRAPLINE" + if invert: + logo += "_WHITE" + else: + logo += "_CMYK" + logo += ".png" file_loc = files("zutil").joinpath("assets") / logo return str(file_loc) diff --git a/zutil/post/post.py b/zutil/post/post.py index 2502d4e..af887dc 100644 --- a/zutil/post/post.py +++ b/zutil/post/post.py @@ -247,7 +247,11 @@ def calc_force_wall( def vtk_logo_stamp( - input: any, location="Upper Left Corner", logo_file=None, strapline=False + input: any, + location="Upper Left Corner", + logo_file=None, + strapline=False, + invert=False, ): """stamps a render view with a zcfd logo Inputs: @@ -262,7 +266,7 @@ def vtk_logo_stamp( if logo_file: logo_path = logo_file else: - logo_path = _get_logo_path(strapline) + logo_path = _get_logo_path(strapline, invert) logo_texture = pvs.CreateTexture(logo_path) logo1.Texture = logo_texture @@ -276,7 +280,7 @@ def vtk_logo_stamp( def vtk_text_stamp( input: any, text_str: str, - fontsize=40, + fontsize=50, color=[1.0, 1.0, 1.0], location="Lower Left Corner", bold=False,