Metadata-Version: 2.5
Name: pyofel
Version: 0.2.3
Summary: Python bindings for Ofel, via pythonnet and Ofel.PythonInterop.
Project-URL: Homepage, https://github.com/se-benjamin-carron/pyofel
Project-URL: Repository, https://github.com/se-benjamin-carron/pyofel
Author: se-benjamin-carron
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Requires-Dist: pythonnet>=3.0
Provides-Extra: dev
Requires-Dist: matplotlib; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Description-Content-Type: text/markdown

# pyofel

Python bindings for [`Ofel`](https://github.com/se-benjamin-carron/ofel),
via [pythonnet](https://github.com/pythonnet/pythonnet) and the
`Ofel.PythonInterop` project. Lets Python/ML code build and solve `Ofel`
finite-element structures without hand-rolling the CoreCLR bootstrap.

## Setup

### Installing the prebuilt package (Windows)

```bash
pip install pyofel
```

No .NET SDK or runtime is required — the published Windows wheel bundles a
private copy of the .NET 9 runtime alongside the published `Ofel.PythonInterop`
output, so `pip install pyofel` is the entire setup.

### Building from source (contributors / non-Windows)

- Python >= 3.10
- **.NET 9 SDK** — required locally to build/install this package this way.

Clone with submodules (this repo vendors a pinned copy of `ofel` at
`vendor/ofel` to build `Ofel.PythonInterop` against):

```bash
git clone --recurse-submodules https://github.com/se-benjamin-carron/pyofel
cd pyofel
```

If you already cloned without `--recurse-submodules`:

```bash
git submodule update --init --recursive
```

Install (this runs a build hook that `dotnet publish`s
`vendor/ofel/src/Ofel.PythonInterop` and stages the output into
`pyofel/_interop/`):

```bash
pip install -e .[dev]
```

Run the tests:

```bash
pytest
```

To reproduce the prebuilt-wheel build locally (Windows only), fetch a
win-x64 .NET 9 runtime-only install and point `PYOFEL_BUNDLE_RUNTIME_DIR` at
it before building:

```bash
PYOFEL_BUNDLE_RUNTIME_DIR=/path/to/dotnet-runtime-win-x64 python -m build --wheel
```

This bundles that runtime into the wheel and tags it as a platform-specific
`win_amd64` wheel instead of pure Python — see `.github/workflows/release.yml`
for the exact CI sequence that fetches the runtime and builds/validates this.

## Usage

```python
from pyofel.data.catalog import get_steel_material, get_steel_section
from pyofel.finite_element import FiniteElementStructure, Point, PointMemberData
from pyofel.member_builder import MemberBuilder

section = get_steel_section("HEA", "HEA200")
material = get_steel_material("S235", "NF EN 10025-2")

builder = MemberBuilder()
column = builder.new_member("Column")
builder.set_points(column, [PointMemberData(0.0, Point(0, 0, 0), section), PointMemberData(1.0, Point(0, 0, 3), section)], material)
builder.set_default_fixed_epsilons(column)
builder.add_fixed_support(column, 0.0)

structure, fe_members = FiniteElementStructure.solve([column])
```

`pyofel.member_builder.MemberBuilder` is the Pythonic, snake_case mirror of
`Ofel.Core.MemberDirector` - build a `Member` by hand (points, supports,
forces, characteristics), then solve it with
`FiniteElementStructure.solve()`. See `pyofel/tools/debug_portal_frame.py`
for a fuller example (a 2-column + 1-beam portal frame, verified and
auto-sized).

`pyofel.evaluation` wraps the higher-level `Ofel.API.Services.StructureEvaluationService`
pipeline (full building input -> climatic loads -> FEM -> Eurocode 3 steel
verification); `pyofel.fixtures` has ready-made example inputs for every
supported gantry shape (Building1, Butterfly, DoubleCanopy, SimpleCanopy),
and `pyofel.governing` picks out the one ULS/SLS result that actually
matters:

```python
from pyofel.evaluation import evaluate
from pyofel.fixtures import create_building1_example_input
from pyofel.governing import uls_ratios

structure_input = create_building1_example_input()
members, structure = evaluate(structure_input)
ratios = uls_ratios(members)
```

Each `create_*_example_input` function is a direct port of one of
vendor/ofel's `Ofel.API.Tests` `UIFixtures` fixtures, building a full
`StructureEvaluationInputUI` (wind/snow loads, roofing plates/purlins, one
gantry, project settings) from `pyofel.inputs`'s Pythonic builders.

`pyofel.tools.debug_portal_frame` hand-builds a 2-column + 1-beam portal
frame directly via `MemberBuilder` - for cases (like an arbitrary extra point
load at one position) the GUI-level pipeline above has no input for.

## Updating the pinned `ofel` version

```bash
cd vendor/ofel
git fetch --tags
git checkout <new-tag-or-commit>
cd ../..
git add vendor/ofel
git commit -m "chore: bump vendor/ofel to <new-tag-or-commit>"
```
