├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yaml │ ├── documentation.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── labels.yml ├── release-drafter.yml └── workflows │ ├── ci.yml │ ├── greetings.yml │ ├── labeler.yml │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .python-version ├── .readthedocs.yaml ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codecov.yml ├── docs ├── _static │ ├── cad_files │ │ ├── box_section.dxf │ │ ├── rhino.3dm │ │ ├── rhino_brep.json │ │ └── rhino_compound.3dm │ ├── favicon.ico │ ├── logo-dark-mode.png │ ├── logo-light-mode.png │ ├── theory │ │ ├── arbitrary-section.png │ │ ├── int1.png │ │ ├── int2.png │ │ ├── int3.png │ │ ├── isoparametric.png │ │ └── six-noded-triangle.png │ └── validation │ │ ├── peery_6-2-1.png │ │ ├── peery_7-2-1_1.png │ │ └── peery_7-2-1_2.png ├── _templates │ ├── custom-class-template.rst │ ├── custom-function-template.rst │ └── custom-module-template.rst ├── api.rst ├── codeofconduct.rst ├── conf.py ├── contributing.rst ├── examples.rst ├── examples │ ├── advanced │ │ ├── advanced_plot.ipynb │ │ ├── matplotlibrc │ │ ├── rectangle_torsion.ipynb │ │ └── trapezoidal_torsion.ipynb │ ├── analysis │ │ ├── frame_analysis.ipynb │ │ ├── geometric_analysis.ipynb │ │ ├── matplotlibrc │ │ ├── plastic_analysis.ipynb │ │ ├── stress_analysis.ipynb │ │ └── warping_analysis.ipynb │ ├── geometry │ │ ├── advanced_geometry.ipynb │ │ ├── create_mesh.ipynb │ │ ├── geometry_cad.ipynb │ │ ├── geometry_coordinates.ipynb │ │ ├── geometry_manipulation.ipynb │ │ ├── matplotlibrc │ │ └── section_library.ipynb │ ├── materials │ │ ├── assign_materials.ipynb │ │ ├── composite_analysis.ipynb │ │ └── matplotlibrc │ ├── results │ │ ├── display_results.ipynb │ │ ├── export_fibre_section.ipynb │ │ ├── get_results.ipynb │ │ ├── get_stress.ipynb │ │ ├── matplotlibrc │ │ ├── plot_centroids.ipynb │ │ └── plot_stress.ipynb │ └── validation │ │ ├── images │ │ ├── arc-geom.png │ │ ├── arc-mesh.png │ │ ├── channel-geom.png │ │ ├── channel-mesh.png │ │ ├── comp-geom.png │ │ └── comp-mesh.png │ │ ├── peery.ipynb │ │ ├── pilkey_arc.ipynb │ │ ├── pilkey_channel.ipynb │ │ └── pilkey_composite.ipynb ├── index.rst ├── installation.rst ├── license.rst ├── user_guide.rst └── user_guide │ ├── analysis.rst │ ├── geometry.rst │ ├── materials.rst │ ├── meshing.rst │ ├── overview.rst │ ├── results.rst │ ├── theory.rst │ └── validation.rst ├── pyproject.toml ├── ruff.toml ├── src └── sectionproperties │ ├── __init__.py │ ├── analysis │ ├── __init__.py │ ├── fea.py │ ├── plastic_section.py │ ├── section.py │ └── solver.py │ ├── post │ ├── __init__.py │ ├── fibre.py │ ├── post.py │ └── stress_post.py │ ├── pre │ ├── __init__.py │ ├── bisect_section.py │ ├── geometry.py │ ├── library │ │ ├── __init__.py │ │ ├── bridge_sections.py │ │ ├── concrete_sections.py │ │ ├── nastran_sections.py │ │ ├── primitive_sections.py │ │ ├── steel_sections.py │ │ ├── timber_sections.py │ │ └── utils.py │ ├── pre.py │ └── rhino.py │ └── py.typed ├── tests ├── __init__.py ├── analysis │ ├── __init__.py │ ├── test_plastic.py │ ├── test_stress.py │ └── test_yield_moment.py ├── benchmarks │ ├── __init__.py │ ├── conftest.py │ ├── test_benchmark_analysis.py │ ├── test_benchmark_geom.py │ └── test_benchmark_mesh.py ├── geometry │ ├── 3in x 2in.3dm │ ├── __init__.py │ ├── complex_shape.3dm │ ├── complex_shape.txt │ ├── compound_shape.3dm │ ├── compound_shape.txt │ ├── rhino_data.json │ ├── section_holes.dxf │ ├── test_geometry.py │ ├── test_offset.py │ └── test_perimeter.py ├── post │ ├── __init__.py │ ├── test_get_results.py │ ├── test_post.py │ └── test_stress_post.py ├── section_library │ ├── __init__.py │ ├── test_concrete_sections.py │ ├── test_steel_sections.py │ └── test_timber_sections.py └── validation │ ├── __init__.py │ ├── test_angle_validation.py │ ├── test_custom_validation.py │ ├── test_peery.py │ ├── test_pilkey.py │ └── test_rectangle_validation.py ├── typings ├── cad_to_shapely │ ├── __init__.pyi │ ├── dxf.pyi │ └── utils.pyi ├── cytriangle │ └── __init__.pyi ├── mpl_toolkits │ └── axes_grid1 │ │ └── axes_divider.pyi ├── numba │ └── __init__.pyi ├── pypardiso │ └── __init__.pyi ├── rhino_shapely_interop │ └── importers.pyi └── scipy │ ├── optimize │ └── __init__.pyi │ └── sparse │ ├── __init__.pyi │ └── linalg.pyi └── uv.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: Did you find a bug? 4 | labels: bug 5 | --- 6 | 7 | 15 | 16 | 17 | 18 | - **sectionproperties version**: 19 | - **Python version**: 20 | - **OS version and name**: 21 | 22 | 23 | 24 | - [ ] I am on the [latest](https://github.com/robbievanleeuwen/section-properties/releases/latest) 25 | stable sectionproperties version, installed using a recommended method. 26 | - [ ] I have searched the [issues](https://github.com/robbievanleeuwen/section-properties/issues) 27 | of this repo and believe that this is not a duplicate. 28 | - [ ] I have consulted the [documentation](https://sectionproperties.readthedocs.io/) 29 | for any relevant information. 30 | 31 | ## Issue 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: "💬 Discussions" 4 | url: https://github.com/robbievanleeuwen/section-properties/discussions 5 | about: | 6 | Ask questions about using sectionproperties, sectionproperties's features and roadmap, or get support and feedback for your usage of sectionproperties. 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "📚 Documentation Issue" 3 | about: Did you find errors, omissions, or anything unintelligible in the documentation? 4 | labels: documentation 5 | --- 6 | 7 | 13 | 14 | 18 | 19 | - [ ] I have searched the [issues](https://github.com/robbievanleeuwen/section-properties/issues) 20 | of this repo and believe that this is not a duplicate. 21 | 22 | ## Issue 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ Feature Request" 3 | about: Do you have ideas for new features or improvements? 4 | labels: enhancement 5 | --- 6 | 7 | 13 | 14 | 18 | 19 | - [ ] I have searched the [issues](https://github.com/robbievanleeuwen/section-properties/issues) 20 | of this repo and believe that this is not a duplicate. 21 | - [ ] I have searched the [documentation](https://sectionproperties.readthedocs.io/) and 22 | believe that my question is not already covered. 23 | 24 | ## Feature Request 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request Check List 2 | 3 | Resolves: #issue-number-here 4 | 5 | 9 | 10 | - [ ] Added a description of your pull request below. 11 | - [ ] Added **tests** for changed code. 12 | - [ ] Updated **documentation** for changed code. 13 | - [ ] Run `uv run pre-commit run --all-files` and `uv run pyright` to ensure code quality. 14 | 15 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Labels names are important as they are used by Release Drafter to decide 3 | # regarding where to record them in changelog or if to skip them. 4 | # 5 | # The repository labels will be automatically configured using this file and 6 | # the GitHub Action https://github.com/marketplace/actions/github-labeler. 7 | 8 | - name: breaking 9 | description: Breaking Changes 10 | color: bfd4f2 11 | 12 | - name: bug 13 | description: Something isn't working 14 | color: d73a4a 15 | 16 | - name: build 17 | description: Build System and Dependencies 18 | color: bfdadc 19 | 20 | - name: ci 21 | description: Continuous Integration 22 | color: 4a97d6 23 | 24 | - name: dependencies 25 | description: Pull requests that update a dependency file 26 | color: 0366d6 27 | 28 | - name: documentation 29 | description: Improvements or additions to documentation 30 | color: 0075ca 31 | 32 | - name: engineering 33 | description: Issue related to engineering methods/assumptions 34 | color: e8d52c 35 | 36 | - name: duplicate 37 | description: This issue or pull request already exists 38 | color: cfd3d7 39 | 40 | - name: enhancement 41 | description: New feature or request 42 | color: a2eeef 43 | 44 | - name: github_actions 45 | description: Pull requests that update Github_actions code 46 | color: "000000" 47 | 48 | - name: good first issue 49 | description: Good for newcomers 50 | color: 7057ff 51 | 52 | - name: help wanted 53 | description: Extra attention is needed 54 | color: 33aa3f 55 | 56 | - name: invalid 57 | description: This doesn't seem right 58 | color: e4e669 59 | 60 | - name: performance 61 | description: Performance 62 | color: "016175" 63 | 64 | - name: python 65 | description: Pull requests that update Python code 66 | color: 2b67c6 67 | 68 | - name: question 69 | description: Further information is requested 70 | color: d876e3 71 | 72 | - name: refactoring 73 | description: Refactoring 74 | color: ef67c4 75 | 76 | - name: removal 77 | description: Removals and Deprecations 78 | color: 9ae7ea 79 | 80 | - name: style 81 | description: Style 82 | color: c120e5 83 | 84 | - name: testing 85 | description: Testing 86 | color: b1fc6f 87 | 88 | - name: wontfix 89 | description: This will not be worked on 90 | color: ffffff 91 | 92 | - name: stale 93 | description: This issue hasn't received enough love 94 | color: 922A20 95 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: ":boom: Breaking Changes" 3 | label: "breaking" 4 | 5 | - title: ":rocket: Features" 6 | label: "enhancement" 7 | 8 | - title: ":fire: Removals and Deprecations" 9 | label: "removal" 10 | 11 | - title: ":bug: Fixes" 12 | label: "bug" 13 | 14 | - title: ":racehorse: Performance" 15 | label: "performance" 16 | 17 | - title: ":rotating_light: Testing" 18 | label: "testing" 19 | 20 | - title: ":construction_worker: Continuous Integration" 21 | label: "ci" 22 | 23 | - title: ":books: Documentation" 24 | label: "documentation" 25 | 26 | - title: ":hammer: Refactoring" 27 | label: "refactoring" 28 | 29 | - title: ":lipstick: Style" 30 | label: "style" 31 | 32 | - title: ":package: Dependencies" 33 | labels: 34 | - "dependencies" 35 | - "build" 36 | 37 | category-template: "### $TITLE" 38 | name-template: "v$RESOLVED_VERSION" 39 | tag-template: "v$RESOLVED_VERSION" 40 | 41 | version-resolver: 42 | major: 43 | labels: 44 | - "major" 45 | minor: 46 | labels: 47 | - "minor" 48 | patch: 49 | labels: 50 | - "patch" 51 | default: patch 52 | 53 | # Custom text at start of release 54 | header: > 55 | 56 | This release adds a rectangular CLT geometry to the section library and updates several dependencies! 57 | 58 | template: | 59 | 60 | ## What's Changed 61 | 62 | $CHANGES 63 | 64 | footer: | 65 | 66 | **Full changelog:** [$PREVIOUS_TAG...v$RESOLVED_VERSION](https://github.com/robbievanleeuwen/section-properties/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION) 67 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | 10 | env: 11 | UV_VERSION: 0.6.17 12 | DEFAULT_PYTHON_VERSION: '3.13' 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | pre-commit: 20 | name: pre-commit 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: Check out the repo 25 | uses: actions/checkout@v4 26 | 27 | - name: Install uv version ${{ env.UV_VERSION }} 28 | uses: astral-sh/setup-uv@v6 29 | with: 30 | version: ${{ env.UV_VERSION }} 31 | enable-cache: true 32 | 33 | - name: Install python ${{ env.DEFAULT_PYTHON_VERSION }} using uv 34 | run: uv python install ${{ env.DEFAULT_PYTHON_VERSION }} 35 | 36 | - name: Install dependencies 37 | run: uv sync -p ${{ env.DEFAULT_PYTHON_VERSION }} --frozen --only-group lint 38 | 39 | - name: Run pre-commit 40 | run: uv run -p ${{ env.DEFAULT_PYTHON_VERSION }} --no-sync pre-commit run --all-files 41 | --color always --show-diff-on-failure 42 | 43 | type-checking: 44 | name: type-checking 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - name: Check out the repo 49 | uses: actions/checkout@v4 50 | 51 | - name: Install uv version ${{ env.UV_VERSION }} 52 | uses: astral-sh/setup-uv@v6 53 | with: 54 | version: ${{ env.UV_VERSION }} 55 | enable-cache: true 56 | 57 | - name: Install python ${{ env.DEFAULT_PYTHON_VERSION }} using uv 58 | run: uv python install ${{ env.DEFAULT_PYTHON_VERSION }} 59 | 60 | - name: Install dependencies 61 | run: uv sync -p ${{ env.DEFAULT_PYTHON_VERSION }} --frozen --all-extras --no-group 62 | dev --no-group docs --no-group test 63 | 64 | - name: Run pyright 65 | run: uv run -p ${{ env.DEFAULT_PYTHON_VERSION }} --no-sync pyright 66 | 67 | tests: 68 | name: ${{ matrix.session }} ${{ matrix.python }} [${{ matrix.os }}] 69 | runs-on: ${{ matrix.os }} 70 | strategy: 71 | fail-fast: false 72 | matrix: 73 | include: 74 | - {python: '3.13', os: ubuntu-latest, session: tests} 75 | - {python: '3.12', os: ubuntu-latest, session: tests} 76 | - {python: '3.11', os: ubuntu-latest, session: tests} 77 | - {python: '3.13', os: windows-latest, session: tests} 78 | - {python: '3.12', os: windows-latest, session: tests} 79 | - {python: '3.11', os: windows-latest, session: tests} 80 | - {python: '3.13', os: macos-latest, session: tests} 81 | - {python: '3.12', os: macos-latest, session: tests} 82 | - {python: '3.11', os: macos-latest, session: tests} 83 | - {python: '3.13', os: macos-13, session: tests} 84 | - {python: '3.12', os: macos-13, session: tests} 85 | - {python: '3.11', os: macos-13, session: tests} 86 | - {python: '3.13', os: ubuntu-latest, session: tests-extended} 87 | 88 | steps: 89 | - name: Check out the repo 90 | uses: actions/checkout@v4 91 | 92 | - name: Install uv version ${{ env.UV_VERSION }} 93 | uses: astral-sh/setup-uv@v6 94 | with: 95 | version: ${{ env.UV_VERSION }} 96 | enable-cache: true 97 | 98 | - name: Install python ${{ matrix.python }} using uv 99 | run: uv python install ${{ matrix.python }} 100 | 101 | - name: Install test dependencies 102 | if: matrix.session != 'tests-extended' 103 | run: uv sync -p ${{ matrix.python }} --frozen --extra rhino --extra dxf --no-group 104 | dev --no-group docs --no-group lint 105 | 106 | - name: Install extended test dependencies 107 | if: matrix.session == 'tests-extended' 108 | run: uv sync -p ${{ matrix.python }} --frozen --all-extras --no-group dev --no-group 109 | docs --no-group lint 110 | 111 | - name: Run pytest 112 | run: uv run -p ${{ matrix.python }} --no-sync coverage run --parallel-mode -m 113 | pytest -m 'not benchmark_suite' --junitxml=junit.xml -o junit_family=legacy 114 | 115 | - name: Upload coverage data 116 | uses: actions/upload-artifact@v4 117 | with: 118 | name: coverage-data-${{ matrix.session }}-${{ matrix.os }}-${{ matrix.python 119 | }} 120 | include-hidden-files: true 121 | path: .coverage.* 122 | 123 | - name: Upload test results to Codecov 124 | if: matrix.session == 'tests-extended' 125 | uses: codecov/test-results-action@v1 126 | with: 127 | token: ${{ secrets.CODECOV_TOKEN }} 128 | 129 | docs-build: 130 | name: docs-build 131 | runs-on: ubuntu-latest 132 | 133 | steps: 134 | - name: Check out the repo 135 | uses: actions/checkout@v4 136 | 137 | - name: Install uv version ${{ env.UV_VERSION }} 138 | uses: astral-sh/setup-uv@v6 139 | with: 140 | version: ${{ env.UV_VERSION }} 141 | enable-cache: true 142 | 143 | - name: Install python ${{ env.DEFAULT_PYTHON_VERSION }} using uv 144 | run: uv python install ${{ env.DEFAULT_PYTHON_VERSION }} 145 | 146 | - name: Install dependencies 147 | run: uv sync -p ${{ env.DEFAULT_PYTHON_VERSION }} --frozen --extra rhino --extra 148 | dxf --no-group dev --no-group lint --no-group test 149 | 150 | - name: Install pandoc 151 | uses: pandoc/actions/setup@v1 152 | 153 | - name: Build docs 154 | run: uv run -p ${{ env.DEFAULT_PYTHON_VERSION }} --no-sync sphinx-build --color 155 | docs docs/_build 156 | 157 | - name: Upload docs 158 | uses: actions/upload-artifact@v4 159 | with: 160 | name: docs 161 | path: docs/_build 162 | 163 | coverage: 164 | name: coverage 165 | runs-on: ubuntu-latest 166 | needs: tests 167 | 168 | steps: 169 | - name: Check out the repo 170 | uses: actions/checkout@v4 171 | 172 | - name: Install uv version ${{ env.UV_VERSION }} 173 | uses: astral-sh/setup-uv@v6 174 | with: 175 | version: ${{ env.UV_VERSION }} 176 | enable-cache: true 177 | 178 | - name: Install python ${{ env.DEFAULT_PYTHON_VERSION }} using uv 179 | run: uv python install ${{ env.DEFAULT_PYTHON_VERSION }} 180 | 181 | - name: Install dependencies 182 | run: uv sync -p ${{ env.DEFAULT_PYTHON_VERSION }} --frozen --only-group test 183 | 184 | - name: Download coverage data 185 | uses: actions/download-artifact@v4 186 | with: 187 | pattern: coverage-data-* 188 | merge-multiple: true 189 | 190 | - name: Combine coverage data 191 | run: uv run -p ${{ env.DEFAULT_PYTHON_VERSION }} --no-sync coverage combine 192 | 193 | - name: Display coverage report 194 | run: uv run -p ${{ env.DEFAULT_PYTHON_VERSION }} --no-sync coverage report -i 195 | 196 | - name: Create coverage report 197 | run: uv run -p ${{ env.DEFAULT_PYTHON_VERSION }} --no-sync coverage xml -i 198 | 199 | - name: Upload coverage report 200 | uses: codecov/codecov-action@v5 201 | with: 202 | token: ${{ secrets.CODECOV_TOKEN }} 203 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: 'Thanks for opening your first issue in ``sectionproperties`` 16 | :raised_hands: Pull requests are always welcome :wink:' 17 | pr-message: 'Thank you for your contribution to ``sectionproperties`` :pray: 18 | We will be reviewing your PR shortly :monocle_face:' 19 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Labeler 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | labeler: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out the repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Run Labeler 16 | uses: crazy-max/ghaction-github-labeler@v5 17 | with: 18 | skip-delete: true 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | UV_VERSION: 0.6.17 10 | DEFAULT_PYTHON_VERSION: '3.13' 11 | 12 | jobs: 13 | release: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | permissions: 17 | id-token: write 18 | contents: write 19 | 20 | steps: 21 | - name: Check out the repository 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 2 25 | 26 | - name: Install uv version ${{ env.UV_VERSION }} 27 | uses: astral-sh/setup-uv@v6 28 | with: 29 | version: ${{ env.UV_VERSION }} 30 | 31 | - name: Install python ${{ env.DEFAULT_PYTHON_VERSION }} using uv 32 | run: uv python install ${{ env.DEFAULT_PYTHON_VERSION }} 33 | 34 | - name: Get previous commit SHA 35 | run: echo "sha=$(git rev-parse --verify --quiet HEAD^)" >> $GITHUB_ENV 36 | 37 | - name: Detect and tag new version 38 | id: check-version 39 | if: ${{ env.sha }} 40 | uses: salsify/action-detect-and-tag-new-version@v2 41 | with: 42 | version-command: echo $(grep "^version =" pyproject.toml | sed 's/version 43 | = "\(.*\)"/\1/') 44 | 45 | - name: Bump version for dev release 46 | if: '! steps.check-version.outputs.tag' 47 | run: | 48 | VERSION=$(grep "^version =" pyproject.toml | sed 's/version = "\(.*\)"/\1/') 49 | uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version $VERSION.dev.$(date +%s) 50 | 51 | - name: Build package 52 | run: uv build 53 | 54 | - name: Publish package on PyPI 55 | if: steps.check-version.outputs.tag 56 | uses: pypa/gh-action-pypi-publish@release/v1 57 | 58 | - name: Publish package on TestPyPI 59 | if: '! steps.check-version.outputs.tag' 60 | uses: pypa/gh-action-pypi-publish@release/v1 61 | with: 62 | repository-url: https://test.pypi.org/legacy/ 63 | 64 | - name: Publish the release notes 65 | uses: release-drafter/release-drafter@v6 66 | with: 67 | publish: ${{ steps.check-version.outputs.tag != '' }} 68 | tag: ${{ steps.check-version.outputs.tag }} 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # venv 10 | .venv 11 | 12 | # doc generated 13 | docs/_build 14 | docs/gen 15 | 16 | # pytest 17 | .pytest_cache 18 | .benchmarks/ 19 | 20 | # coverage reports 21 | .coverage 22 | .coverage.* 23 | htmlcov/ 24 | 25 | # jupyter notebook 26 | .ipynb_checkpoints 27 | 28 | # ruff 29 | .ruff_cache 30 | 31 | # mac 32 | .DS_Store 33 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-yaml 6 | - id: check-toml 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | - repo: https://github.com/astral-sh/ruff-pre-commit 10 | rev: v0.11.7 11 | hooks: 12 | - id: ruff 13 | args: [--fix] 14 | - id: ruff-format 15 | - repo: https://github.com/kynan/nbstripout 16 | rev: 0.8.1 17 | hooks: 18 | - id: nbstripout 19 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-24.04 5 | tools: 6 | python: "3.13" 7 | jobs: 8 | # https://docs.readthedocs.com/platform/stable/build-customization.html#install-dependencies-with-uv 9 | pre_create_environment: 10 | - asdf plugin add uv 11 | - asdf install uv latest 12 | - asdf global uv latest 13 | - uv python install 3.13 14 | create_environment: 15 | - uv venv -p 3.13 16 | install: 17 | - uv sync -p 3.13 --frozen --extra rhino --extra dxf --no-group dev --no-group lint --no-group test 18 | build: 19 | html: 20 | - uv run -p 3.13 --no-sync sphinx-build -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html 21 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: "1.2.0" 2 | authors: 3 | - family-names: Leeuwen 4 | given-names: Robbie 5 | name-particle: van 6 | orcid: "https://orcid.org/0009-0004-8056-3977" 7 | - family-names: Ferster 8 | given-names: Connor 9 | orcid: "https://orcid.org/0009-0005-0861-2428" 10 | doi: 10.21105/joss.06105 11 | message: If you use this software, please cite our article in the 12 | Journal of Open Source Software. 13 | preferred-citation: 14 | authors: 15 | - family-names: Leeuwen 16 | given-names: Robbie 17 | name-particle: van 18 | orcid: "https://orcid.org/0009-0004-8056-3977" 19 | - family-names: Ferster 20 | given-names: Connor 21 | orcid: "https://orcid.org/0009-0005-0861-2428" 22 | date-published: 2024-04-19 23 | doi: 10.21105/joss.06105 24 | issn: 2475-9066 25 | issue: 96 26 | journal: Journal of Open Source Software 27 | publisher: 28 | name: Open Journals 29 | start: 6105 30 | title: "sectionproperties: A Python package for the analysis of 31 | arbitrary cross-sections using the finite element method" 32 | type: article 33 | url: "https://joss.theoj.org/papers/10.21105/joss.06105" 34 | volume: 9 35 | title: "sectionproperties: A Python package for the analysis of 36 | arbitrary cross-sections using the finite element method" 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [robbie.vanleeuwen@gmail.com](mailto:robbie.vanleeuwen@gmail.com). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][faq]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [mozilla coc]: https://github.com/mozilla/diversity 131 | [faq]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Guide 2 | 3 | Thank you for your interest in improving this project. This project is open-source under the [MIT license] and welcomes contributions in the form of bug reports, feature requests, and pull requests. 4 | 5 | Here is a list of important resources for contributors: 6 | 7 | - [Source Code] 8 | - [Documentation] 9 | - [Issue Tracker] 10 | - [Code of Conduct] 11 | 12 | [mit license]: https://opensource.org/licenses/MIT 13 | [source code]: https://github.com/robbievanleeuwen/section-properties 14 | [documentation]: https://sectionproperties.readthedocs.io/ 15 | [issue tracker]: https://github.com/robbievanleeuwen/section-properties/issues 16 | 17 | ## How to report a bug 18 | 19 | Report bugs on the [Issue Tracker]. 20 | 21 | When filing an issue, make sure to answer these questions: 22 | 23 | - Which operating system and Python version are you using? 24 | - Which version of this project are you using? 25 | - What did you do? 26 | - What did you expect to see? 27 | - What did you see instead? 28 | 29 | The best way to get your bug fixed is to provide a test case, and/or steps to reproduce the issue. 30 | 31 | ## How to request a feature 32 | 33 | Features that improve `sectionproperties` can be suggested on the [Issue Tracker]. It's a good idea to first submit the proposal as a feature request prior to submitting a pull request as this allows for the best coordination of efforts by preventing the duplication of work, and allows for feedback on your ideas. 34 | 35 | ## How to set up your development environment 36 | 37 | `sectionproperties` uses `uv` for python project management. `uv` can be installed on using the standalone installer: 38 | 39 | ```shell 40 | curl -LsSf https://astral.sh/uv/install.sh | sh 41 | ``` 42 | 43 | Installation instructions for other methods and Windows can be found [here](https://docs.astral.sh/uv/getting-started/installation/). 44 | 45 | `uv` can then be used to install the latest compatible version of python: 46 | 47 | ```shell 48 | uv python install 3.13 49 | ``` 50 | 51 | `sectionproperties` and it's development dependencies can be installed with: 52 | 53 | ```shell 54 | uv sync 55 | ``` 56 | 57 | Specific extras (e.g. `numba`) can be installed with the `--extra` flag or all extras with the `--all-extras` flag: 58 | 59 | ```shell 60 | uv sync --extra numba 61 | uv sync --all-extras 62 | ``` 63 | 64 | If you want to build the documentation locally, you will need to install `pandoc`. The [installation method](https://pandoc.org/installing.html) depends on what OS you are running. 65 | 66 | To run a script using the development virtual environment, you can run: 67 | 68 | ```shell 69 | uv run example.py 70 | ``` 71 | 72 | Refer to the `uv` [documentation](https://docs.astral.sh/uv/) for more information relating to using `uv` for project management. 73 | 74 | ## How to test the project 75 | 76 | ### Pre-commit 77 | 78 | [Pre-commit](https://pre-commit.com/) ensures code quality and consistency by running the `ruff` linter and formatter, stripping out execution cells in jupyter notebooks, and running several pre-commit hooks. 79 | 80 | These can be run against all files in the project with: 81 | 82 | ```shell 83 | uv run pre-commit run --all-files 84 | ``` 85 | 86 | However, the best way to ensure code quality is by installing the git pre-commit hook: 87 | 88 | ```shell 89 | uv run pre-commit install 90 | ``` 91 | 92 | This will run `pre-commit` against all changed files when attempting to `git commit`. You will need to fix the offending files prior to being able to commit a change unless you run `git commit --no-verify`. 93 | 94 | ### Type Checking 95 | 96 | `sectionproperties` uses `pyright` to ensure strict type-checking where possible. `pyright` can be run on all files with: 97 | 98 | ```shell 99 | uv run pyright 100 | ``` 101 | 102 | ### Tests 103 | 104 | The `sectionproperties` tests are located in the `tests/` directory and are written using the [pytest] testing framework. The test suite can be run with: 105 | 106 | ```shell 107 | uv run pytest -m 'not benchmark_suite' 108 | ``` 109 | 110 | #### Benchmarks 111 | 112 | If the code you are modifying may affect the performance of `sectionproperties`, it is recommended that you run the benchmarking tests to verify the performance before and after your changes. There are three different benchmarking suites: `geometry`, `meshing` and `analysis`. These can be run like this: 113 | 114 | ```shell 115 | uv run pytest -m benchmark_geom 116 | uv run pytest -m benchmark_mesh 117 | uv run pytest -m benchmark_analysis 118 | ``` 119 | 120 | Note that a plot of the results can be generated by adding the `--benchmark-histogram` option to the above commands. 121 | 122 | [pytest]: https://pytest.readthedocs.io/ 123 | 124 | ### Documentation 125 | 126 | You can build the documentation locally with: 127 | 128 | ```shell 129 | uv run sphinx-build docs docs/_build 130 | ``` 131 | 132 | Make sure that you have a recent version of `pandoc` installed so that the example notebooks can be generated. 133 | 134 | Note that all pull requests also build the documentation on Read the Docs, so building the documentation locally is not required. 135 | 136 | ## How to submit changes 137 | 138 | Open a [pull request] to submit changes to this project. 139 | 140 | Your pull request needs to meet the following guidelines for acceptance: 141 | 142 | - The test suite, pre-commit and pyright checks must pass without errors and warnings. 143 | - Include unit tests. This project aims for a high code coverage. 144 | - If your changes add functionality, update the documentation accordingly. 145 | 146 | It is recommended to open an issue before starting work on anything. This will allow a chance to talk it over with the owners and validate your approach. 147 | 148 | [pull request]: https://github.com/robbievanleeuwen/section-properties/pulls 149 | [code of conduct]: CODE_OF_CONDUCT.md 150 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2025 Robbie van Leeuwen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sectionproperties logo 5 | 6 | 7 | [![PyPI](https://img.shields.io/pypi/v/sectionproperties.svg)][pypi_] 8 | [![Status](https://img.shields.io/pypi/status/sectionproperties.svg)][status] 9 | [![Python Version](https://img.shields.io/pypi/pyversions/sectionproperties)][python version] 10 | [![License](https://img.shields.io/pypi/l/sectionproperties)][license] 11 | [![Read the documentation at https://sectionproperties.readthedocs.io/](https://img.shields.io/readthedocs/sectionproperties/stable.svg?label=Read%20the%20Docs)][read the docs] 12 | [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)][uv] 13 | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)][ruff] 14 | [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)][pre-commit] 15 | [![Tests](https://github.com/robbievanleeuwen/section-properties/actions/workflows/ci.yml/badge.svg?branch=master)][tests] 16 | [![Codecov](https://codecov.io/gh/robbievanleeuwen/section-properties/branch/master/graph/badge.svg)][codecov] 17 | [![DOI](https://joss.theoj.org/papers/10.21105/joss.06105/status.svg)][joss] 18 | 19 | [pypi_]: https://pypi.org/project/sectionproperties/ 20 | [status]: https://pypi.org/project/sectionproperties/ 21 | [python version]: https://pypi.org/project/sectionproperties 22 | [read the docs]: https://sectionproperties.readthedocs.io/ 23 | [uv]: https://github.com/astral-sh/uv 24 | [ruff]: https://github.com/astral-sh/ruff 25 | [pre-commit]: https://github.com/pre-commit/pre-commit 26 | [tests]: https://github.com/robbievanleeuwen/section-properties/actions/workflows/ci.yml 27 | [codecov]: https://app.codecov.io/gh/robbievanleeuwen/section-properties 28 | [joss]: https://doi.org/10.21105/joss.06105 29 | 30 | `sectionproperties` is a python package for the analysis of arbitrary cross-sections 31 | using the finite element method. `sectionproperties` can be used to determine 32 | section properties to be used in structural design and visualise cross-sectional 33 | stresses resulting from combinations of applied forces and bending moments. 34 | 35 | [Subscribe](http://eepurl.com/dMMUeg) to the `sectionproperties` mailing list! 36 | 37 | ## Installation 38 | 39 | You can install `sectionproperties` via [pip] from [PyPI]: 40 | 41 | ```shell 42 | pip install sectionproperties 43 | ``` 44 | 45 | ## Documentation 46 | 47 | `sectionproperties` is fully documented including a user walkthrough, examples, 48 | background theory and an API guide. The documentation can found at 49 | [https://sectionproperties.readthedocs.io/](https://sectionproperties.readthedocs.io/). 50 | 51 | ## Features 52 | 53 | See the complete list of `sectionproperties` features 54 | [here](https://sectionproperties.readthedocs.io/en/stable/user_guide.html). 55 | 56 | ## Contributing 57 | 58 | Contributions are very welcome. To learn more, see the [Contributor Guide]. 59 | 60 | ## License 61 | 62 | Distributed under the terms of the [MIT license][license], `sectionproperties` is free 63 | and open source software. 64 | 65 | ## Support 66 | 67 | Found a bug 🐛, or have a feature request ✨, raise an issue on the 68 | GitHub [issue tracker](https://github.com/robbievanleeuwen/section-properties/issues) 69 | Alternatively you can get support on the 70 | [discussions](https://github.com/robbievanleeuwen/section-properties/discussions) page. 71 | 72 | ## Disclaimer 73 | 74 | `sectionproperties` is an open source engineering tool that continues to benefit from 75 | the collaboration of many contributors. Although efforts have been made to ensure the 76 | that relevant engineering theories have been correctly implemented, it remains the 77 | user's responsibility to confirm and accept the output. Refer to the 78 | [license][license] for clarification of the conditions of use. 79 | 80 | [pypi]: https://pypi.org/ 81 | [pip]: https://pip.pypa.io/ 82 | [license]: https://github.com/robbievanleeuwen/section-properties/blob/master/LICENSE 83 | [contributor guide]: https://github.com/robbievanleeuwen/section-properties/blob/master/CONTRIBUTING.md 84 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | precision: 2 5 | round: nearest 6 | range: "50...90" 7 | status: 8 | project: 9 | default: 10 | # basic 11 | target: auto 12 | threshold: 5% 13 | base: auto 14 | patch: 15 | default: 16 | # basic 17 | target: auto 18 | threshold: 5% 19 | base: auto 20 | -------------------------------------------------------------------------------- /docs/_static/cad_files/rhino.3dm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/cad_files/rhino.3dm -------------------------------------------------------------------------------- /docs/_static/cad_files/rhino_brep.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 10000, 3 | "archive3dm": 60, 4 | "opennurbs": -1910998424, 5 | "data": "+n8CAAUMAAAAAAAA+/8CABQAAAAAAAAAxdu1YGDm0xG/5AAQgwEi8JhRycz8/wIAzQsAAAAAAAAzAIAAQEEDAAAAAAAAEAQAAAABAAAA+n8CAL4AAAAAAAAA+/8CABQAAAAAAAAA3dTXTkfp0xG/5QAQgwEi8BDyeHD8/wIAhgAAAAAAAAARAgAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAJzGZuv9/AoAAAAAAAAAAAAEAAAD6fwIAvgAAAAAAAAD7/wIAFAAAAAAAAADd1NdOR+nTEb/lABCDASLwEPJ4cPz/AgCGAAAAAAAAABECAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAADwPwIAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAZ0+Yh/38CgAAAAAAAAAAAAQAAAPp/AgC+AAAAAAAAAPv/AgAUAAAAAAAAAN3U105H6dMRv+UAEIMBIvAQ8nhw/P8CAIYAAAAAAAAAEQIAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAPA/AgAAAAAAAAAAAPA/AAAAAAAA8D8AAAAAAAAAAAAAAAAAAPA/AOZUmFb/fwKAAAAAAAAAAAABAAAA+n8CAL4AAAAAAAAA+/8CABQAAAAAAAAA3dTXTkfp0xG/5QAQgwEi8BDyeHD8/wIAhgAAAAAAAAARAgAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8CAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAA2Lbnzf9/AoAAAAAAAAAAADuG1gUAgABATQIAAAAAAAAQBAAAAAEAAAD6fwIAgQAAAAAAAAD7/wIAFAAAAAAAAADb1NdOR+nTEb/lABCDASLw9JvpI/z/AgBJAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AwAAABT68D7/fwKAAAAAAAAAAAABAAAA+n8CAIEAAAAAAAAA+/8CABQAAAAAAAAA29TXTkfp0xG/5QAQgwEi8PSb6SP8/wIASQAAAAAAAAAQAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwMAAAAT+VQy/38CgAAAAAAAAAAAAQAAAPp/AgCBAAAAAAAAAPv/AgAUAAAAAAAAANvU105H6dMRv+UAEIMBIvD0m+kj/P8CAEkAAAAAAAAAEAAAAAAAAPA/AAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8DAAAACicWQv9/AoAAAAAAAAAAAAEAAAD6fwIAgQAAAAAAAAD7/wIAFAAAAAAAAADb1NdOR+nTEb/lABCDASLw9JvpI/z/AgBJAAAAAAAAABAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AwAAAA0ksk7/fwKAAAAAAAAAAAA7htYFAIAAQBYBAAAAAAAAEAEAAAABAAAA+n8CAP0AAAAAAAAA+/8CABQAAAAAAAAA39TXTkfp0xG/5QAQgwEi8HPX2Pf8/wIAxQAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAIAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAPA/0P8rhv9/AoAAAAAAAAAAACQx3z4AgABAyQAAAAAAAAAQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAADwPwAAAAAAAPA/AAAAAAAAAAACAAAAAQAAAAIAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAACAAAAAgAAAAMAAAAAAAAAAAAAABvksB8AgABAGQEAAAAAAAAQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwEAAAACAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8CAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AgAAAAMAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwMAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8DAAAAAAAAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/iuMUrQCAAEAZAgAAAAAAABAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAABAAAAAAAAAAEAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/AQAAAAEAAAAAAAAAAAAAAAAAAAAAAPA/AQAAAAEAAAACAAAAAAAAAAEAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/AgAAAAIAAAAAAAAAAAAAAAAAAAAAAPA/AgAAAAIAAAADAAAAAAAAAAEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/AwAAAAMAAAAAAAAAAAAAAAAAAAAAAPA/AwAAAAMAAAAAAAAAAAAAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/I/TaqgCAAEApAAAAAAAAABABAAAAAAAAAAQAAAAAAAAAAQAAAAIAAAADAAAAAQAAAAAAAABzz7mMAIAAQDEAAAAAAAAAEQEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUfCC4wAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAAAAAAAAAAAAAAAAACAAEAFAAAAAAAAAACN7wLSAIAAQAUAAAAAAAAAAI3vAtIAAAAAAIAAQA0AAAAAAAAAAQAAAAEAAAAAXSkSzLgALVz/fwKAAAAAAAAAAAA=" 6 | } 7 | -------------------------------------------------------------------------------- /docs/_static/cad_files/rhino_compound.3dm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/cad_files/rhino_compound.3dm -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/logo-dark-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/logo-dark-mode.png -------------------------------------------------------------------------------- /docs/_static/logo-light-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/logo-light-mode.png -------------------------------------------------------------------------------- /docs/_static/theory/arbitrary-section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/theory/arbitrary-section.png -------------------------------------------------------------------------------- /docs/_static/theory/int1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/theory/int1.png -------------------------------------------------------------------------------- /docs/_static/theory/int2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/theory/int2.png -------------------------------------------------------------------------------- /docs/_static/theory/int3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/theory/int3.png -------------------------------------------------------------------------------- /docs/_static/theory/isoparametric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/theory/isoparametric.png -------------------------------------------------------------------------------- /docs/_static/theory/six-noded-triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/theory/six-noded-triangle.png -------------------------------------------------------------------------------- /docs/_static/validation/peery_6-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/validation/peery_6-2-1.png -------------------------------------------------------------------------------- /docs/_static/validation/peery_7-2-1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/validation/peery_7-2-1_1.png -------------------------------------------------------------------------------- /docs/_static/validation/peery_7-2-1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/_static/validation/peery_7-2-1_2.png -------------------------------------------------------------------------------- /docs/_templates/custom-class-template.rst: -------------------------------------------------------------------------------- 1 | {{ name | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. autoclass:: {{ objname }} 6 | :members: 7 | :show-inheritance: 8 | :inherited-members: 9 | :special-members: __call__, __add__, __mul__, __sub__, __or__, __xor__, __and__ 10 | 11 | {% block methods %} 12 | {% if methods %} 13 | .. rubric:: {{ _('Methods') }} 14 | 15 | .. autosummary:: 16 | :nosignatures: 17 | {% for item in methods %} 18 | {%- if not item.startswith('_') %} 19 | ~{{ name }}.{{ item }} 20 | {%- endif -%} 21 | {%- endfor %} 22 | {% endif %} 23 | {% endblock %} 24 | 25 | {% block attributes %} 26 | {% if attributes %} 27 | .. rubric:: {{ _('Attributes') }} 28 | 29 | .. autosummary:: 30 | {% for item in attributes %} 31 | ~{{ name }}.{{ item }} 32 | {%- endfor %} 33 | {% endif %} 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /docs/_templates/custom-function-template.rst: -------------------------------------------------------------------------------- 1 | {{ name | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. auto{{ objtype }}:: {{ objname }} 6 | -------------------------------------------------------------------------------- /docs/_templates/custom-module-template.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline}} 2 | 3 | .. automodule:: {{ fullname }} 4 | 5 | {% block attributes %} 6 | {% if attributes %} 7 | .. rubric:: Module attributes 8 | 9 | .. autosummary:: 10 | :toctree: 11 | {% for item in attributes %} 12 | {{ item }} 13 | {%- endfor %} 14 | {% endif %} 15 | {% endblock %} 16 | 17 | {% block functions %} 18 | {% if functions %} 19 | .. rubric:: {{ _('Functions') }} 20 | 21 | .. autosummary:: 22 | :toctree: 23 | :template: custom-function-template.rst 24 | :nosignatures: 25 | {% for item in functions %} 26 | {{ item }} 27 | {%- endfor %} 28 | {% endif %} 29 | {% endblock %} 30 | 31 | {% block classes %} 32 | {% if classes %} 33 | .. rubric:: {{ _('Classes') }} 34 | 35 | .. autosummary:: 36 | :toctree: 37 | :template: custom-class-template.rst 38 | :nosignatures: 39 | {% for item in classes %} 40 | {{ item }} 41 | {%- endfor %} 42 | {% endif %} 43 | {% endblock %} 44 | 45 | {% block exceptions %} 46 | {% if exceptions %} 47 | .. rubric:: {{ _('Exceptions') }} 48 | 49 | .. autosummary:: 50 | :toctree: 51 | {% for item in exceptions %} 52 | {{ item }} 53 | {%- endfor %} 54 | {% endif %} 55 | {% endblock %} 56 | 57 | {% block modules %} 58 | {% if modules %} 59 | .. autosummary:: 60 | :toctree: 61 | :template: custom-module-template.rst 62 | :recursive: 63 | {% for item in modules %} 64 | {{ item }} 65 | {%- endfor %} 66 | {% endif %} 67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | ``sectionproperties`` Modules 5 | ----------------------------- 6 | 7 | .. autosummary:: 8 | :toctree: gen 9 | :caption: API Reference 10 | :template: custom-module-template.rst 11 | :recursive: 12 | 13 | sectionproperties.pre 14 | sectionproperties.analysis 15 | sectionproperties.post 16 | -------------------------------------------------------------------------------- /docs/codeofconduct.rst: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | ==================================== 3 | 4 | Our Pledge 5 | ---------- 6 | 7 | We as members, contributors, and leaders pledge to make participation in 8 | our community a harassment-free experience for everyone, regardless of 9 | age, body size, visible or invisible disability, ethnicity, sex 10 | characteristics, gender identity and expression, level of experience, 11 | education, socio-economic status, nationality, personal appearance, 12 | race, caste, color, religion, or sexual identity and orientation. 13 | 14 | We pledge to act and interact in ways that contribute to an open, 15 | welcoming, diverse, inclusive, and healthy community. 16 | 17 | Our Standards 18 | ------------- 19 | 20 | Examples of behavior that contributes to a positive environment for our 21 | community include: 22 | 23 | - Demonstrating empathy and kindness toward other people 24 | - Being respectful of differing opinions, viewpoints, and experiences 25 | - Giving and gracefully accepting constructive feedback 26 | - Accepting responsibility and apologizing to those affected by our 27 | mistakes, and learning from the experience 28 | - Focusing on what is best not just for us as individuals, but for the 29 | overall community 30 | 31 | Examples of unacceptable behavior include: 32 | 33 | - The use of sexualized language or imagery, and sexual attention or 34 | advances of any kind 35 | - Trolling, insulting or derogatory comments, and personal or political 36 | attacks 37 | - Public or private harassment 38 | - Publishing others' private information, such as a physical or email 39 | address, without their explicit permission 40 | - Other conduct which could reasonably be considered inappropriate in a 41 | professional setting 42 | 43 | Enforcement Responsibilities 44 | ---------------------------- 45 | 46 | Community leaders are responsible for clarifying and enforcing our 47 | standards of acceptable behavior and will take appropriate and fair 48 | corrective action in response to any behavior that they deem 49 | inappropriate, threatening, offensive, or harmful. 50 | 51 | Community leaders have the right and responsibility to remove, edit, or 52 | reject comments, commits, code, wiki edits, issues, and other 53 | contributions that are not aligned to this Code of Conduct, and will 54 | communicate reasons for moderation decisions when appropriate. 55 | 56 | Scope 57 | ----- 58 | 59 | This Code of Conduct applies within all community spaces, and also 60 | applies when an individual is officially representing the community in 61 | public spaces. Examples of representing our community include using an 62 | official e-mail address, posting via an official social media account, 63 | or acting as an appointed representative at an online or offline event. 64 | 65 | Enforcement 66 | ----------- 67 | 68 | Instances of abusive, harassing, or otherwise unacceptable behavior may 69 | be reported to the community leaders responsible for enforcement at 70 | robbie.vanleeuwen@gmail.com. All complaints will be reviewed and 71 | investigated promptly and fairly. 72 | 73 | All community leaders are obligated to respect the privacy and security 74 | of the reporter of any incident. 75 | 76 | Enforcement Guidelines 77 | ---------------------- 78 | 79 | Community leaders will follow these Community Impact Guidelines in 80 | determining the consequences for any action they deem in violation of 81 | this Code of Conduct: 82 | 83 | 1. Correction 84 | ~~~~~~~~~~~~~ 85 | 86 | **Community Impact**: Use of inappropriate language or other behavior 87 | deemed unprofessional or unwelcome in the community. 88 | 89 | **Consequence**: A private, written warning from community leaders, 90 | providing clarity around the nature of the violation and an explanation 91 | of why the behavior was inappropriate. A public apology may be 92 | requested. 93 | 94 | 2. Warning 95 | ~~~~~~~~~~ 96 | 97 | **Community Impact**: A violation through a single incident or series of 98 | actions. 99 | 100 | **Consequence**: A warning with consequences for continued behavior. No 101 | interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, for a specified period of 103 | time. This includes avoiding interactions in community spaces as well as 104 | external channels like social media. Violating these terms may lead to a 105 | temporary or permanent ban. 106 | 107 | 3. Temporary Ban 108 | ~~~~~~~~~~~~~~~~ 109 | 110 | **Community Impact**: A serious violation of community standards, 111 | including sustained inappropriate behavior. 112 | 113 | **Consequence**: A temporary ban from any sort of interaction or public 114 | communication with the community for a specified period of time. No 115 | public or private interaction with the people involved, including 116 | unsolicited interaction with those enforcing the Code of Conduct, is 117 | allowed during this period. Violating these terms may lead to a 118 | permanent ban. 119 | 120 | 4. Permanent Ban 121 | ~~~~~~~~~~~~~~~~ 122 | 123 | **Community Impact**: Demonstrating a pattern of violation of community 124 | standards, including sustained inappropriate behavior, harassment of an 125 | individual, or aggression toward or disparagement of classes of 126 | individuals. 127 | 128 | **Consequence**: A permanent ban from any sort of public interaction 129 | within the community. 130 | 131 | Attribution 132 | ----------- 133 | 134 | This Code of Conduct is adapted from the `Contributor 135 | Covenant `__, version 2.1, 136 | available at 137 | https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. 138 | 139 | Community Impact Guidelines were inspired by `Mozilla's code of conduct 140 | enforcement ladder `__. 141 | 142 | For answers to common questions about this code of conduct, see the FAQ 143 | at https://www.contributor-covenant.org/faq. Translations are available 144 | at https://www.contributor-covenant.org/translations. 145 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | """Sphinx configuration.""" 2 | 3 | # project information 4 | project = "sectionproperties" 5 | author = "Robbie van Leeuwen" 6 | copyright = "2025, Robbie van Leeuwen" # noqa: A001 7 | 8 | # sphinx config 9 | templates_path = ["_templates"] 10 | exclude_patterns = ["_build"] 11 | 12 | extensions = [ 13 | "sphinx.ext.autodoc", 14 | "sphinx.ext.autosummary", 15 | "sphinx.ext.intersphinx", 16 | "sphinx.ext.mathjax", 17 | "sphinx.ext.napoleon", 18 | "sphinx.ext.viewcode", 19 | "matplotlib.sphinxext.plot_directive", 20 | "nbsphinx", 21 | "sphinx_copybutton", 22 | "sphinxext.opengraph", 23 | ] 24 | 25 | # autodoc config 26 | autodoc_member_order = "bysource" 27 | autodoc_typehints = "both" 28 | autodoc_typehints_description_target = "documented_params" 29 | 30 | # napoleon config 31 | napoleon_numpy_docstring = False 32 | napoleon_include_init_with_doc = True 33 | napoleon_use_admonition_for_examples = True 34 | napoleon_use_ivar = True 35 | 36 | # nbsphinx config 37 | nbsphinx_execute_arguments = [ 38 | "--InlineBackend.figure_formats={'svg', 'pdf'}", 39 | "--InlineBackend.rc=figure.dpi=96", 40 | ] 41 | 42 | # intersphinx mapping 43 | intersphinx_mapping = { 44 | "python": ("https://docs.python.org/3", None), 45 | "numpy": ("https://numpy.org/doc/stable/", None), 46 | "scipy": ("https://docs.scipy.org/doc/scipy/", None), 47 | "matplotlib": ("https://matplotlib.org/stable/", None), 48 | "shapely": ("https://shapely.readthedocs.io/en/stable/", None), 49 | } 50 | 51 | # html theme 52 | html_theme = "furo" 53 | html_static_path = ["_static"] 54 | html_favicon = "_static/favicon.ico" 55 | html_theme_options = { 56 | "light_logo": "logo-light-mode.png", # add light mode logo 57 | "dark_logo": "logo-dark-mode.png", # add dark mode logo 58 | "sidebar_hide_name": True, # hide name of project in sidebar (already in logo) 59 | "source_repository": "https://github.com/robbievanleeuwen/section-properties", 60 | "source_branch": "master", 61 | "source_directory": "docs/", 62 | } 63 | pygments_style = "sphinx" 64 | pygments_dark_style = "monokai" 65 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _label-contributing: 2 | 3 | Contributor Guide 4 | ================= 5 | 6 | Thank you for your interest in improving this project. This project is open-source 7 | under the `MIT license `__ and welcomes 8 | contributions in the form of bug reports, feature requests, and pull requests. 9 | 10 | Here is a list of important resources for contributors: 11 | 12 | - `Source Code `__ 13 | - `Documentation `__ 14 | - `Issue Tracker `__ 15 | - :doc:`Code of Conduct ` 16 | 17 | How to report a bug 18 | ------------------- 19 | 20 | Report bugs on the `Issue Tracker `__. 21 | 22 | When filing an issue, make sure to answer these questions: 23 | 24 | - Which operating system and Python version are you using? 25 | - Which version of this project are you using? 26 | - What did you do? 27 | - What did you expect to see? 28 | - What did you see instead? 29 | 30 | The best way to get your bug fixed is to provide a test case, and/or steps to reproduce 31 | the issue. 32 | 33 | How to request a feature 34 | ------------------------ 35 | 36 | Features that improve ``sectionproperties`` can be suggested on the 37 | `Issue Tracker `__. 38 | It's a good idea to first submit the proposal as a feature request prior to submitting a 39 | pull request as this allows for the best coordination of efforts by preventing the 40 | duplication of work, and allows for feedback on your ideas. 41 | 42 | How to set up your development environment 43 | ------------------------------------------ 44 | ``sectionproperties`` uses ``uv`` for python project management. ``uv`` can be installed 45 | on using the standalone installer: 46 | 47 | .. code:: shell 48 | 49 | curl -LsSf https://astral.sh/uv/install.sh | sh 50 | 51 | Installation instructions for other methods and Windows can be found 52 | `here `__. 53 | 54 | ``uv`` can then be used to install the latest compatible version of python: 55 | 56 | .. code:: shell 57 | 58 | uv python install 3.13 59 | 60 | ``sectionproperties`` and it's development dependencies can be installed with: 61 | 62 | .. code:: shell 63 | 64 | uv sync 65 | 66 | Specific extras (e.g. ``numba``) can be installed with the ``--extra`` flag or all 67 | extras with the ``--all-extras`` flag: 68 | 69 | .. code:: shell 70 | 71 | uv sync --extra numba 72 | uv sync --all-extras 73 | 74 | If you want to build the documentation locally, you will need to install ``pandoc``. The 75 | `installation method `__ depends on what OS you are 76 | running. 77 | 78 | To run a script using the development virtual environment, you can run: 79 | 80 | .. code:: shell 81 | 82 | uv run example.py 83 | 84 | Refer to the ``uv`` `documentation `__ for more information 85 | relating to using ``uv`` for project management. 86 | 87 | How to test the project 88 | ----------------------- 89 | 90 | Pre-commit 91 | ^^^^^^^^^^ 92 | 93 | `Pre-commit `__ ensures code quality and consistency by running 94 | the ``ruff`` linter and formatter, stripping out execution cells in jupyter notebooks, 95 | and running several pre-commit hooks. 96 | 97 | These can be run against all files in the project with: 98 | 99 | .. code:: shell 100 | 101 | uv run pre-commit run --all-files 102 | 103 | However, the best way to ensure code quality is by installing the git pre-commit hook: 104 | 105 | .. code:: shell 106 | 107 | uv run pre-commit install 108 | 109 | This will run ``pre-commit`` against all changed files when attempting to 110 | ``git commit``. You will need to fix the offending files prior to being able to commit a 111 | change unless you run ``git commit --no-verify``. 112 | 113 | Type Checking 114 | ^^^^^^^^^^^^^ 115 | 116 | ``sectionproperties`` uses ``pyright`` to ensure strict type-checking where possible. 117 | ``pyright`` can be run on all files with: 118 | 119 | .. code:: shell 120 | 121 | uv run pyright 122 | 123 | Tests 124 | ^^^^^ 125 | 126 | The ``sectionproperties`` tests are located in the ``tests/`` directory and are written 127 | using the `pytest `__ testing framework. The test suite 128 | can be run with: 129 | 130 | .. code:: shell 131 | 132 | uv run pytest -m 'not benchmark_suite' 133 | 134 | Benchmarks 135 | """""""""" 136 | 137 | If the code you are modifying may affect the performance of ``sectionproperties``, it is 138 | recommended that you run the benchmarking tests to verify the performance before and 139 | after your changes. There are three different benchmarking suites: ``geometry``, 140 | ``meshing`` and ``analysis``. These can be run like this: 141 | 142 | .. code:: shell 143 | 144 | uv run pytest -m benchmark_geom 145 | uv run pytest -m benchmark_mesh 146 | uv run pytest -m benchmark_analysis 147 | 148 | Note that a plot of the results can be generated by adding the ``--benchmark-histogram`` 149 | option to the above commands. 150 | 151 | Documentation 152 | ------------- 153 | 154 | You can build the documentation locally with: 155 | 156 | .. code:: shell 157 | 158 | uv run sphinx-build docs docs/_build 159 | 160 | Make sure that you have a recent version of ``pandoc`` installed so that the example 161 | notebooks can be generated. 162 | 163 | Note that all pull requests also build the documentation on Read the Docs, so building 164 | the documentation locally is not required. 165 | 166 | How to submit changes 167 | --------------------- 168 | 169 | Open a `pull request `__ 170 | to submit changes to this project. 171 | 172 | Your pull request needs to meet the following guidelines for acceptance: 173 | 174 | - The test suite, pre-commit and pyright checks must pass without errors and warnings. 175 | - Include unit tests. This project aims for a high code coverage. 176 | - If your changes add functionality, update the documentation 177 | accordingly. 178 | 179 | It is recommended to open an issue before starting work on anything. 180 | This will allow a chance to talk it over with the owners and validate 181 | your approach. 182 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | The following examples showcase the features of ``sectionproperties`` in an 5 | instructional manner, while also highlighting several validation tests and advanced 6 | applications. The below examples are available as jupyter notebooks and can be 7 | downloaded 8 | `here `_ 9 | (click on the file you would like to download, then click the download icon at the top 10 | of the file). 11 | 12 | To run these notebooks you must first install `jupyter notebook `_ 13 | by running ``pip install notebook`` in the same virtual environment in which 14 | ``sectionproperties`` is installed. Next, navigate to the location of the downloaded 15 | examples and run ``jupyter notebook`` to open jupyter in the browser. Finally, double 16 | click an example file to open the notebook, and execute each cell by clicking the play 17 | button. More information on jupyter notebooks can be found 18 | `here `_. Don't be afraid to 19 | `raise an issue `_ or 20 | `post in the discussions page `_ 21 | if you have trouble running any of the examples! 22 | 23 | Geometry 24 | -------- 25 | 26 | .. nbgallery:: 27 | :name: geometry-gallery 28 | :maxdepth: 1 29 | 30 | examples/geometry/geometry_coordinates 31 | examples/geometry/section_library 32 | examples/geometry/geometry_manipulation 33 | examples/geometry/geometry_cad 34 | examples/geometry/advanced_geometry 35 | examples/geometry/create_mesh 36 | 37 | Materials 38 | --------- 39 | 40 | .. nbgallery:: 41 | :name: materials-gallery 42 | :maxdepth: 1 43 | 44 | examples/materials/assign_materials 45 | examples/materials/composite_analysis 46 | 47 | Analysis 48 | -------- 49 | 50 | .. nbgallery:: 51 | :name: analysis-gallery 52 | :maxdepth: 1 53 | 54 | examples/analysis/geometric_analysis 55 | examples/analysis/plastic_analysis 56 | examples/analysis/warping_analysis 57 | examples/analysis/frame_analysis 58 | examples/analysis/stress_analysis 59 | 60 | Results 61 | ------- 62 | 63 | .. nbgallery:: 64 | :name: results-gallery 65 | :maxdepth: 1 66 | 67 | examples/results/display_results 68 | examples/results/get_results 69 | examples/results/plot_centroids 70 | examples/results/plot_stress 71 | examples/results/get_stress 72 | examples/results/export_fibre_section 73 | 74 | Validation 75 | ---------- 76 | 77 | .. nbgallery:: 78 | :name: validation-gallery 79 | :maxdepth: 1 80 | 81 | examples/validation/pilkey_channel 82 | examples/validation/pilkey_arc 83 | examples/validation/pilkey_composite 84 | examples/validation/peery 85 | 86 | Advanced 87 | -------- 88 | 89 | .. nbgallery:: 90 | :name: advanced-gallery 91 | :maxdepth: 1 92 | 93 | examples/advanced/advanced_plot 94 | examples/advanced/rectangle_torsion 95 | examples/advanced/trapezoidal_torsion 96 | -------------------------------------------------------------------------------- /docs/examples/advanced/advanced_plot.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Advanced Plotting" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1", 14 | "metadata": {}, 15 | "source": [ 16 | "All plots in `sectionproperties` allow keyword arguments to be passed to [plotting_context()](../..//gen/sectionproperties.post.post.plotting_context.rst#sectionproperties.post.post.plotting_context). This example shows one application of using the `plotting_context()` to generate a custom plot in a 2 x 2 arrangement." 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "2", 22 | "metadata": {}, 23 | "source": [ 24 | "## Create Geometry and Perform Analysis" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "3", 30 | "metadata": {}, 31 | "source": [ 32 | "We start by creating the geometry for a 100 x 6 SHS." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "id": "4", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "from sectionproperties.pre.library import rectangular_hollow_section\n", 43 | "\n", 44 | "geom = rectangular_hollow_section(d=100, b=100, t=6, r_out=15, n_r=8)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "id": "5", 50 | "metadata": {}, 51 | "source": [ 52 | "Next we create a mesh and a `Section` object." 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "id": "6", 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "from sectionproperties.analysis import Section\n", 63 | "\n", 64 | "geom.create_mesh(mesh_sizes=[5])\n", 65 | "sec = Section(geometry=geom)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "7", 71 | "metadata": {}, 72 | "source": [ 73 | "We can now perform a stress analysis by applying a 10 kN.m torsion (after first running the geometric and warping analysis)." 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "id": "8", 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [ 83 | "sec.calculate_geometric_properties()\n", 84 | "sec.calculate_warping_properties()\n", 85 | "stress = sec.calculate_stress(mzz=10e6)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "9", 91 | "metadata": {}, 92 | "source": [ 93 | "## Generate Plot\n", 94 | "\n", 95 | "We are going to generate a plot of the geometry, mesh, centroids and stress. In the first plot, we will setup the parameters of the entire figure. We make sure we set `render=False` to prevent the plot from displaying before we are finished. In the subsequent plots we pass the axis we would like to display the plot on." 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "10", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "import matplotlib.pyplot as plt\n", 106 | "\n", 107 | "# plot the geometry\n", 108 | "ax = geom.plot_geometry(\n", 109 | " labels=[],\n", 110 | " nrows=2,\n", 111 | " ncols=2,\n", 112 | " figsize=(12, 7),\n", 113 | " render=False,\n", 114 | ")\n", 115 | "\n", 116 | "# get the figure object from the first plot\n", 117 | "fig = ax.get_figure()\n", 118 | "\n", 119 | "# plot the mesh\n", 120 | "sec.plot_mesh(materials=False, ax=fig.axes[1])\n", 121 | "\n", 122 | "# plot the centroids\n", 123 | "sec.plot_centroids(ax=fig.axes[2])\n", 124 | "\n", 125 | "# plot the torsion stress\n", 126 | "stress.plot_stress(\n", 127 | " stress=\"mzz_zxy\",\n", 128 | " normalize=False,\n", 129 | " ax=fig.axes[3],\n", 130 | ")\n", 131 | "\n", 132 | "# finally display the plot\n", 133 | "plt.show()" 134 | ] 135 | } 136 | ], 137 | "metadata": { 138 | "kernelspec": { 139 | "display_name": "Python 3 (ipykernel)", 140 | "language": "python", 141 | "name": "python3" 142 | }, 143 | "language_info": { 144 | "codemirror_mode": { 145 | "name": "ipython", 146 | "version": 3 147 | }, 148 | "file_extension": ".py", 149 | "mimetype": "text/x-python", 150 | "name": "python", 151 | "nbconvert_exporter": "python", 152 | "pygments_lexer": "ipython3", 153 | "version": "3.9.17" 154 | } 155 | }, 156 | "nbformat": 4, 157 | "nbformat_minor": 5 158 | } 159 | -------------------------------------------------------------------------------- /docs/examples/advanced/matplotlibrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/examples/advanced/matplotlibrc -------------------------------------------------------------------------------- /docs/examples/advanced/rectangle_torsion.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Torsion Constant of a Rectangle" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1", 14 | "metadata": {}, 15 | "source": [ 16 | "This example shows how `sectionproperties` can be used as part of a script to conduct multiple analyses. In this example, we examine how the torsion constant varies with the aspect ratio of a rectangle section." 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "2", 22 | "metadata": {}, 23 | "source": [ 24 | "## Setup Analysis Parameters" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "3", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "import numpy as np\n", 35 | "\n", 36 | "# list of aspect ratios to analyse (10^0 = 1 to 10^1.301 = 20)\n", 37 | "# logspace used to concentrate data near aspect ratio = 1\n", 38 | "aspect_ratios = np.logspace(0, 1.301, 50)\n", 39 | "torsion_constants = [] # list of torsion constant results\n", 40 | "area = 1 # cross-section area\n", 41 | "n = 100 # approximate number of finite elements in each analysis" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "4", 47 | "metadata": {}, 48 | "source": [ 49 | "## Analysis Loop" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "5", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "from sectionproperties.analysis import Section\n", 60 | "from sectionproperties.pre.library import rectangular_section\n", 61 | "\n", 62 | "for ar in aspect_ratios:\n", 63 | " # calculate rectangle dimensions\n", 64 | " d = np.sqrt(ar)\n", 65 | " b = 1 / d\n", 66 | " geom = rectangular_section(d=d, b=b)\n", 67 | "\n", 68 | " # create mesh and Section object\n", 69 | " ms = d * b / n\n", 70 | " geom.create_mesh(mesh_sizes=[ms])\n", 71 | " sec = Section(geometry=geom)\n", 72 | "\n", 73 | " # perform analysis\n", 74 | " sec.calculate_frame_properties()\n", 75 | "\n", 76 | " # save the torsion constant\n", 77 | " torsion_constants.append(sec.get_j())" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "id": "6", 83 | "metadata": {}, 84 | "source": [ 85 | "## Plot Results" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": null, 91 | "id": "7", 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "import matplotlib.pyplot as plt\n", 96 | "\n", 97 | "_, ax = plt.subplots()\n", 98 | "ax.plot(aspect_ratios, torsion_constants, \"k-\")\n", 99 | "ax.set_xlim(0, 21)\n", 100 | "ax.set_ylim(0, 0.15)\n", 101 | "ax.set_xlabel(\"Aspect Ratio [d/b]\")\n", 102 | "ax.set_ylabel(\"Torsion Constant [J]\")\n", 103 | "ax.set_title(\"Rectangular Section Torsion Constant\")\n", 104 | "ax.grid()\n", 105 | "plt.show()" 106 | ] 107 | } 108 | ], 109 | "metadata": { 110 | "kernelspec": { 111 | "display_name": "Python 3 (ipykernel)", 112 | "language": "python", 113 | "name": "python3" 114 | }, 115 | "language_info": { 116 | "codemirror_mode": { 117 | "name": "ipython", 118 | "version": 3 119 | }, 120 | "file_extension": ".py", 121 | "mimetype": "text/x-python", 122 | "name": "python", 123 | "nbconvert_exporter": "python", 124 | "pygments_lexer": "ipython3", 125 | "version": "3.9.17" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 5 130 | } 131 | -------------------------------------------------------------------------------- /docs/examples/analysis/frame_analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Frame Analysis" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1", 14 | "metadata": {}, 15 | "source": [ 16 | "This example demonstrates how to perform a frame analysis in `sectionproperties`.\n", 17 | "\n", 18 | "A `frame` analysis calculates only the cross-section properties required for a frame analysis:\n", 19 | "\n", 20 | "- Cross-section area\n", 21 | "- Second moments of area about the centroidal axis\n", 22 | "- Torsion constant\n", 23 | "- Principal axis angle\n", 24 | "\n", 25 | "As a result, it is significantly more efficient than conducting both `geometric` and `warping` analyses, which is ususally required to obtain the above results." 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "id": "2", 31 | "metadata": {}, 32 | "source": [ 33 | "This example will analyse a 12-sided polygonal hollow section and compare the time taken for a typical warping analysis with that taken for a frame analysis." 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "3", 39 | "metadata": {}, 40 | "source": [ 41 | "## Create Geometry and Section" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "id": "4", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "from sectionproperties.analysis import Section\n", 52 | "from sectionproperties.pre.library import polygon_hollow_section\n", 53 | "\n", 54 | "geom = polygon_hollow_section(d=600, t=12, n_sides=12, r_in=20, n_r=8)\n", 55 | "geom.create_mesh(mesh_sizes=20)\n", 56 | "sec = Section(geometry=geom)\n", 57 | "sec.plot_mesh(materials=False)" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "id": "5", 63 | "metadata": {}, 64 | "source": [ 65 | "## Geometric and Warping Analysis\n", 66 | "\n", 67 | "First we can time how long it takes to perform a geometric and warping analysis." 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "id": "6", 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "import time\n", 78 | "\n", 79 | "start = time.time()\n", 80 | "sec.calculate_geometric_properties()\n", 81 | "sec.calculate_warping_properties()\n", 82 | "end = time.time()\n", 83 | "gw_time = end - start" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "id": "7", 89 | "metadata": {}, 90 | "source": [ 91 | "We can print the time taken and the torsion constant to compare results." 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "id": "8", 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "print(f\"Geometric/Warping Time = {gw_time:.4f} secs\")\n", 102 | "print(f\"J = {sec.get_j():.3e} mm4\")" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "id": "9", 108 | "metadata": {}, 109 | "source": [ 110 | "## Frame Analysis\n", 111 | "\n", 112 | "Now we can time how long it takes to perform a frame analysis." 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "id": "10", 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "start = time.time()\n", 123 | "sec.calculate_frame_properties()\n", 124 | "end = time.time()\n", 125 | "f_time = end - start" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "id": "11", 131 | "metadata": {}, 132 | "source": [ 133 | "Again, we can print the time taken and the torsion constant." 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "id": "12", 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "print(f\"Frame Time = {f_time:.4f} secs\")\n", 144 | "print(f\"J = {sec.get_j():.3e} mm4\")" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "id": "13", 150 | "metadata": {}, 151 | "source": [ 152 | "By not calculating shear functions, shear & warping integrals etc. required for a full warping analysis, significant time is saved if the user only wants frame properties." 153 | ] 154 | } 155 | ], 156 | "metadata": { 157 | "kernelspec": { 158 | "display_name": "Python 3 (ipykernel)", 159 | "language": "python", 160 | "name": "python3" 161 | }, 162 | "language_info": { 163 | "codemirror_mode": { 164 | "name": "ipython", 165 | "version": 3 166 | }, 167 | "file_extension": ".py", 168 | "mimetype": "text/x-python", 169 | "name": "python", 170 | "nbconvert_exporter": "python", 171 | "pygments_lexer": "ipython3", 172 | "version": "3.9.17" 173 | } 174 | }, 175 | "nbformat": 4, 176 | "nbformat_minor": 5 177 | } 178 | -------------------------------------------------------------------------------- /docs/examples/analysis/matplotlibrc: -------------------------------------------------------------------------------- 1 | figure.dpi: 96 2 | -------------------------------------------------------------------------------- /docs/examples/analysis/stress_analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Stress Analysis" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1", 14 | "metadata": {}, 15 | "source": [ 16 | "This example demonstrates how to perform a stress analysis in `sectionproperties`." 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "2", 22 | "metadata": {}, 23 | "source": [ 24 | "In this example we model a 150 x 100 x 6 RHS and subject it to various load cases." 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "3", 30 | "metadata": {}, 31 | "source": [ 32 | "## Create Geometry and Section" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "id": "4", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "from sectionproperties.analysis import Section\n", 43 | "from sectionproperties.pre.library import rectangular_hollow_section\n", 44 | "\n", 45 | "geom = rectangular_hollow_section(d=100, b=150, t=6, r_out=15, n_r=8)\n", 46 | "geom.create_mesh(mesh_sizes=[2])\n", 47 | "sec = Section(geometry=geom)\n", 48 | "sec.plot_mesh(materials=False)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "id": "5", 54 | "metadata": {}, 55 | "source": [ 56 | "## Geometric and Warping Analysis\n", 57 | "\n", 58 | "Because we will be subjecting this box section to torsion and shear, we must first conduct a geometric and warping analysis." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "id": "6", 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "sec.calculate_geometric_properties()\n", 69 | "sec.calculate_warping_properties()" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "id": "7", 75 | "metadata": {}, 76 | "source": [ 77 | "## Perform Stress Analysis\n", 78 | "\n", 79 | "The first load case applies the following loads:\n", 80 | "\n", 81 | "- Mxx = 5 kN.m\n", 82 | "- Vy = -10 kN\n", 83 | "- Mzz = 3 kN.m\n", 84 | "\n", 85 | "The second load case applies the following loads:\n", 86 | "\n", 87 | "- Myy = 15 kN.m\n", 88 | "- Vx = 30 kN\n", 89 | "- Mzz = 1.5 kN.m" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "id": "8", 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "case1 = sec.calculate_stress(mxx=5e6, vy=-10e3, mzz=3e6)\n", 100 | "case2 = sec.calculate_stress(myy=15e6, vx=30e3, mzz=1.5e6)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "id": "9", 106 | "metadata": {}, 107 | "source": [ 108 | "## Plot Streses\n", 109 | "\n", 110 | "`case1` and `case2` obtained from the stress analysis are [StressPost()](../../gen/sectionproperties.post.stress_post.StressPost.rst#sectionproperties.post.stress_post.StressPost) objects. We can plot stresses by using the `plot_stress()` and `plot_stress_vector()` methods that belongs to this object." 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "id": "10", 116 | "metadata": {}, 117 | "source": [ 118 | "### Case 1" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "id": "11", 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "# bending stress\n", 129 | "case1.plot_stress(stress=\"m_zz\")" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "id": "12", 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "# torsion stress vectors\n", 140 | "case1.plot_stress_vector(stress=\"mzz_zxy\")" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "id": "13", 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "# von mises stress\n", 151 | "case1.plot_stress(stress=\"vm\", cmap=\"YlOrRd\", normalize=False)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "id": "14", 157 | "metadata": {}, 158 | "source": [ 159 | "### Case 2" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "id": "15", 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "# shear stress\n", 170 | "case2.plot_stress(stress=\"v_zxy\")" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "id": "16", 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "# von mises stress\n", 181 | "case2.plot_stress(stress=\"vm\", cmap=\"YlOrRd\", normalize=False)" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "id": "17", 187 | "metadata": {}, 188 | "source": [ 189 | "Note that the colormap can be changed using one of those specified [here](https://matplotlib.org/stable/users/explain/colors/colormaps.html). Sequential colormaps are arguably more suited to examining von Mises stresses (always positive), whereas diverging colormaps are more insightful for stresses that can be positive and negative.\n", 190 | "\n", 191 | "The `normalize=True` argument places the centre of the colormap at zero stress." 192 | ] 193 | } 194 | ], 195 | "metadata": { 196 | "kernelspec": { 197 | "display_name": "Python 3 (ipykernel)", 198 | "language": "python", 199 | "name": "python3" 200 | }, 201 | "language_info": { 202 | "codemirror_mode": { 203 | "name": "ipython", 204 | "version": 3 205 | }, 206 | "file_extension": ".py", 207 | "mimetype": "text/x-python", 208 | "name": "python", 209 | "nbconvert_exporter": "python", 210 | "pygments_lexer": "ipython3", 211 | "version": "3.9.17" 212 | } 213 | }, 214 | "nbformat": 4, 215 | "nbformat_minor": 5 216 | } 217 | -------------------------------------------------------------------------------- /docs/examples/analysis/warping_analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Warping Analysis" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1", 14 | "metadata": {}, 15 | "source": [ 16 | "This example demonstrates how to perform a warping analysis in `sectionproperties`." 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "2", 22 | "metadata": {}, 23 | "source": [ 24 | "## Channel Section\n", 25 | "\n", 26 | "This example will conduct a warping analysis on a 250PFC section.\n", 27 | "\n", 28 | "First we create the cross-section geometry." 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "id": "3", 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "from sectionproperties.pre.library import channel_section\n", 39 | "\n", 40 | "geom = channel_section(d=250, b=90, t_f=15, t_w=8, r=12, n_r=8)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "id": "4", 46 | "metadata": {}, 47 | "source": [ 48 | "Next we must create a finite element mesh and a `Section` object. Unlike geometric and plastic analyses, the warping results are mesh dependent. As a general rule of thumb, we would like all plate sections to be at least several elements thick in order to resolve the variations in the warping and shear functions." 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "id": "5", 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "from sectionproperties.analysis import Section\n", 59 | "\n", 60 | "geom.create_mesh(mesh_sizes=7)\n", 61 | "sec = Section(geometry=geom)\n", 62 | "sec.plot_mesh(materials=False)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "6", 68 | "metadata": {}, 69 | "source": [ 70 | "We can now perform the warping analysis by calling `calculate_warping_properties()`, note that a geometric analysis must be performed first. We also compare the `\"direct\"` solver with the `\"cgs\"` solver for speed." 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "id": "7", 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "import time\n", 81 | "\n", 82 | "sec.calculate_geometric_properties()\n", 83 | "\n", 84 | "# direct solver\n", 85 | "start = time.time()\n", 86 | "sec.calculate_warping_properties(solver_type=\"direct\")\n", 87 | "end = time.time()\n", 88 | "print(f\"Direct Solver Time = {end - start:.4f} secs\")\n", 89 | "\n", 90 | "# cgs solver\n", 91 | "start = time.time()\n", 92 | "sec.calculate_warping_properties(solver_type=\"cgs\")\n", 93 | "end = time.time()\n", 94 | "print(f\"CGS Solver Time = {end - start:.4f} secs\")" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "id": "8", 100 | "metadata": {}, 101 | "source": [ 102 | "We can plot the centroids to display the geometric centroid and shear centre using `plot_centroids()`." 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "id": "9", 109 | "metadata": { 110 | "nbsphinx-thumbnail": { 111 | "output-index": 0 112 | } 113 | }, 114 | "outputs": [], 115 | "source": [ 116 | "sec.plot_centroids()" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "id": "10", 122 | "metadata": {}, 123 | "source": [ 124 | "We can also obtain the some relevant warping properties:\n", 125 | "\n", 126 | "- `J` - torsion constant\n", 127 | "- `Iw`/`Gamma` - warping constant\n", 128 | "- `As_y` - shear area for loading along the y-axis" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "id": "11", 135 | "metadata": {}, 136 | "outputs": [], 137 | "source": [ 138 | "print(f\"J = {sec.get_j():.3e} mm4\")\n", 139 | "print(f\"Iw = {sec.get_gamma():.3e} mm6\")\n", 140 | "print(f\"As_y = {sec.get_as()[1]:.1f} mm2\")" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "id": "12", 146 | "metadata": {}, 147 | "source": [ 148 | "## Unconnected Sections" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "id": "13", 154 | "metadata": {}, 155 | "source": [ 156 | "Unlike geometric and plastic analysis, cross-sections analysed for warping must have strict connectivity. `sectionproperties` checks for connectivity prior to conducting a warping analysis and will throw an error if there is not full connectivity between all regions of the mesh." 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": null, 162 | "id": "14", 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "from sectionproperties.pre.library import rectangular_section\n", 167 | "\n", 168 | "# create an unconnected mesh\n", 169 | "rect = rectangular_section(d=10, b=10)\n", 170 | "geom = rect + rect.shift_section(x_offset=20)\n", 171 | "geom.create_mesh(mesh_sizes=1)\n", 172 | "sec = Section(geometry=geom)\n", 173 | "sec.plot_mesh(materials=False)" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": null, 179 | "id": "15", 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "# geometric and plastic analyses can be conducted\n", 184 | "sec.calculate_geometric_properties()\n", 185 | "sec.calculate_plastic_properties()" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "id": "16", 192 | "metadata": { 193 | "editable": true, 194 | "slideshow": { 195 | "slide_type": "" 196 | }, 197 | "tags": [ 198 | "raises-exception" 199 | ] 200 | }, 201 | "outputs": [], 202 | "source": [ 203 | "# warping analysis will fail\n", 204 | "sec.calculate_warping_properties()" 205 | ] 206 | } 207 | ], 208 | "metadata": { 209 | "kernelspec": { 210 | "display_name": "Python 3 (ipykernel)", 211 | "language": "python", 212 | "name": "python3" 213 | }, 214 | "language_info": { 215 | "codemirror_mode": { 216 | "name": "ipython", 217 | "version": 3 218 | }, 219 | "file_extension": ".py", 220 | "mimetype": "text/x-python", 221 | "name": "python", 222 | "nbconvert_exporter": "python", 223 | "pygments_lexer": "ipython3", 224 | "version": "3.9.17" 225 | } 226 | }, 227 | "nbformat": 4, 228 | "nbformat_minor": 5 229 | } 230 | -------------------------------------------------------------------------------- /docs/examples/geometry/geometry_cad.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Geometry from CAD\n", 9 | "\n", 10 | "This example demonstrates loading ``Geometry`` and ``CompoundGeometry`` objects from ``.dxf`` and ``.3dm`` (rhino) files." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "1", 16 | "metadata": {}, 17 | "source": [ 18 | "## Import Modules\n", 19 | "\n", 20 | "We start by importing the necessary modules." 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "2", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "from sectionproperties.analysis import Section\n", 31 | "from sectionproperties.pre import CompoundGeometry, Geometry" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "id": "3", 37 | "metadata": {}, 38 | "source": [ 39 | "## Load from ``.dxf``" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "id": "4", 45 | "metadata": {}, 46 | "source": [ 47 | "### ``Geometry`` object\n", 48 | "\n", 49 | "We can load a single region from a ``.dxf`` file by using the [Geometry.from_dxf()](../../gen/sectionproperties.pre.geometry.Geometry.rst#sectionproperties.pre.geometry.Geometry.from_dxf) method." 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "5", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "geom = Geometry.from_dxf(dxf_filepath=\"../../_static/cad_files/box_section.dxf\")" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "6", 65 | "metadata": {}, 66 | "source": [ 67 | "To display the section, we mesh the geometry and plot the mesh." 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "id": "7", 74 | "metadata": { 75 | "nbsphinx-thumbnail": { 76 | "output-index": 0 77 | } 78 | }, 79 | "outputs": [], 80 | "source": [ 81 | "geom.create_mesh(mesh_sizes=[0.5])\n", 82 | "Section(geometry=geom).plot_mesh(materials=False)" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "id": "8", 88 | "metadata": {}, 89 | "source": [ 90 | "Note that loading multiple regions from a `.dxf` file is not currently supported. A possible work around could involve saving each region as a separate `.dxf` file, importing each region individually using `Geometry.from_dxf()`, then combining the regions using the `+` operator." 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "id": "9", 96 | "metadata": {}, 97 | "source": [ 98 | "## Load from ``.3dm``" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "id": "10", 104 | "metadata": {}, 105 | "source": [ 106 | "### ``Geometry`` object\n", 107 | "\n", 108 | "We can load a single region from a ``.3dm`` file by using the [Geometry.from_3dm()](../../gen/sectionproperties.pre.geometry.Geometry.rst#sectionproperties.pre.geometry.Geometry.from_3dm) method." 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "id": "11", 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "geom = Geometry.from_3dm(filepath=\"../../_static/cad_files/rhino.3dm\")\n", 119 | "geom = geom.rotate_section(angle=90) # rotate for viewability" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "id": "12", 125 | "metadata": {}, 126 | "source": [ 127 | "To display the section, we mesh the geometry and plot the mesh." 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "id": "13", 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "geom.create_mesh(mesh_sizes=[0.005])\n", 138 | "Section(geometry=geom).plot_mesh(materials=False)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "id": "14", 144 | "metadata": {}, 145 | "source": [ 146 | "### ``CompoundGeometry`` object\n", 147 | "\n", 148 | "We can load multiple regions from a ``.3dm`` file by using the [CompoundGeometry.from_3dm()](../../gen/sectionproperties.pre.geometry.CompoundGeometry.rst#sectionproperties.pre.geometry.CompoundGeometry.from_3dm) method." 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": null, 154 | "id": "15", 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "geom = CompoundGeometry.from_3dm(filepath=\"../../_static/cad_files/rhino_compound.3dm\")\n", 159 | "geom = geom.rotate_section(angle=90) # rotate for viewability" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "id": "16", 165 | "metadata": {}, 166 | "source": [ 167 | "To display the section, we mesh the geometry and plot the mesh." 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "id": "17", 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [ 177 | "geom.create_mesh(mesh_sizes=[0.005])\n", 178 | "Section(geometry=geom).plot_mesh(materials=False)" 179 | ] 180 | } 181 | ], 182 | "metadata": { 183 | "kernelspec": { 184 | "display_name": "Python 3 (ipykernel)", 185 | "language": "python", 186 | "name": "python3" 187 | }, 188 | "language_info": { 189 | "codemirror_mode": { 190 | "name": "ipython", 191 | "version": 3 192 | }, 193 | "file_extension": ".py", 194 | "mimetype": "text/x-python", 195 | "name": "python", 196 | "nbconvert_exporter": "python", 197 | "pygments_lexer": "ipython3", 198 | "version": "3.9.17" 199 | } 200 | }, 201 | "nbformat": 4, 202 | "nbformat_minor": 5 203 | } 204 | -------------------------------------------------------------------------------- /docs/examples/geometry/matplotlibrc: -------------------------------------------------------------------------------- 1 | figure.dpi: 96 2 | -------------------------------------------------------------------------------- /docs/examples/materials/matplotlibrc: -------------------------------------------------------------------------------- 1 | figure.dpi: 96 2 | -------------------------------------------------------------------------------- /docs/examples/results/display_results.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Display Results" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1", 14 | "metadata": {}, 15 | "source": [ 16 | "This example demonstrates how to display results in `sectionproperties`.\n", 17 | "\n", 18 | "A 165.1 x 5.4 CHS will be analysed and the differences in the [display_results()](../../gen/sectionproperties.analysis.section.Section.rst#sectionproperties.analysis.section.Section.display_results) output highlighted." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "id": "2", 24 | "metadata": {}, 25 | "source": [ 26 | "## Create Geometry and Section" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "3", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "from sectionproperties.analysis import Section\n", 37 | "from sectionproperties.pre.library import circular_hollow_section\n", 38 | "\n", 39 | "geom = circular_hollow_section(d=165.1, t=5.4, n=64)\n", 40 | "geom.create_mesh(mesh_sizes=10)\n", 41 | "sec = Section(geometry=geom)\n", 42 | "sec.plot_mesh(materials=False)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "4", 48 | "metadata": {}, 49 | "source": [ 50 | "The `display_results()` method will print all the results that have been calculated for the `Section` object. If an analysis has not been conducted, no results will display." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "id": "5", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "sec.display_results()" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "id": "6", 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "sec.calculate_geometric_properties()\n", 71 | "sec.display_results()" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "id": "7", 77 | "metadata": {}, 78 | "source": [ 79 | "The formatting can be changed by passing a formatting string to the `fmt` argument, see [here](https://docs.python.org/3/library/string.html#format-specification-mini-language) for more information on string formatting." 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "id": "8", 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "sec.display_results(fmt=\".1f\")" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "id": "9", 95 | "metadata": {}, 96 | "source": [ 97 | "When more analyses are conducted, more results are displayed." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "id": "10", 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "sec.calculate_warping_properties()\n", 108 | "sec.calculate_plastic_properties()\n", 109 | "sec.display_results()" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "id": "11", 115 | "metadata": {}, 116 | "source": [ 117 | "Because we have not specified any material properties, the displayed properties are purely geometric. If we assign a steel material to the CHS, we will see some results change to material property weighted values (see [here](../../user_guide/results.rst#how-material-properties-affect-results) for more information on how material properties affect results)." 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "id": "12", 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "from sectionproperties.pre import Material\n", 128 | "\n", 129 | "# create steel material\n", 130 | "steel = Material(\n", 131 | " name=\"Steel\",\n", 132 | " elastic_modulus=200e3, # N/mm^2 (MPa)\n", 133 | " poissons_ratio=0.3, # unitless\n", 134 | " density=7.85e-6, # kg/mm^3\n", 135 | " yield_strength=500, # N/mm^2 (MPa)\n", 136 | " color=\"grey\",\n", 137 | ")\n", 138 | "geom.material = steel # assign steel to the CHS\n", 139 | "\n", 140 | "# remesh and recreate Section object\n", 141 | "geom.create_mesh(mesh_sizes=5)\n", 142 | "sec = Section(geometry=geom)\n", 143 | "\n", 144 | "# perform analysis and display results\n", 145 | "sec.calculate_geometric_properties()\n", 146 | "sec.calculate_warping_properties()\n", 147 | "sec.calculate_plastic_properties()\n", 148 | "sec.display_results(fmt=\".3e\")" 149 | ] 150 | } 151 | ], 152 | "metadata": { 153 | "kernelspec": { 154 | "display_name": "Python 3 (ipykernel)", 155 | "language": "python", 156 | "name": "python3" 157 | }, 158 | "language_info": { 159 | "codemirror_mode": { 160 | "name": "ipython", 161 | "version": 3 162 | }, 163 | "file_extension": ".py", 164 | "mimetype": "text/x-python", 165 | "name": "python", 166 | "nbconvert_exporter": "python", 167 | "pygments_lexer": "ipython3", 168 | "version": "3.9.17" 169 | } 170 | }, 171 | "nbformat": 4, 172 | "nbformat_minor": 5 173 | } 174 | -------------------------------------------------------------------------------- /docs/examples/results/matplotlibrc: -------------------------------------------------------------------------------- 1 | figure.dpi: 96 2 | -------------------------------------------------------------------------------- /docs/examples/results/plot_centroids.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Plot Centroids" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1", 14 | "metadata": {}, 15 | "source": [ 16 | "This example demonstrates how to plot centroids in `sectionproperties`.\n", 17 | "\n", 18 | "The `plot_centroids()` method will display a plot of the finite element mesh along with any centroids that have been calculated:\n", 19 | "\n", 20 | "- Geometric analysis - geometric centroid and principal axes\n", 21 | "- Warping analysis - shear centre\n", 22 | "- Plastic analysis - plastic centroid" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "id": "2", 28 | "metadata": {}, 29 | "source": [ 30 | "This example will plot the centroids for a 200 mm deep and 12 mm thick bulb section with a 10 mm radius." 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "id": "3", 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "from sectionproperties.analysis import Section\n", 41 | "from sectionproperties.pre.library import bulb_section\n", 42 | "\n", 43 | "geom = bulb_section(d=200, b=50, t=12, r=10, n_r=8)\n", 44 | "geom.create_mesh(mesh_sizes=20)\n", 45 | "sec = Section(geometry=geom)\n", 46 | "sec.plot_mesh(materials=False)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "id": "4", 52 | "metadata": {}, 53 | "source": [ 54 | "## Geometric Analysis" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "id": "5", 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "sec.calculate_geometric_properties()\n", 65 | "sec.plot_centroids()" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "6", 71 | "metadata": {}, 72 | "source": [ 73 | "## Warping Analysis\n", 74 | "\n", 75 | "Note that the title and transparency can be changed by specifying `title` and `alpha` respectively." 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "id": "7", 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "sec.calculate_warping_properties()\n", 86 | "sec.plot_centroids(title=\"Geometric & Warping Centroids\", alpha=0.2)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "id": "8", 92 | "metadata": {}, 93 | "source": [ 94 | "## Plastic Analysis" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "id": "9", 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "sec.calculate_plastic_properties()\n", 105 | "sec.plot_centroids()" 106 | ] 107 | } 108 | ], 109 | "metadata": { 110 | "kernelspec": { 111 | "display_name": "Python 3 (ipykernel)", 112 | "language": "python", 113 | "name": "python3" 114 | }, 115 | "language_info": { 116 | "codemirror_mode": { 117 | "name": "ipython", 118 | "version": 3 119 | }, 120 | "file_extension": ".py", 121 | "mimetype": "text/x-python", 122 | "name": "python", 123 | "nbconvert_exporter": "python", 124 | "pygments_lexer": "ipython3", 125 | "version": "3.9.17" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 5 130 | } 131 | -------------------------------------------------------------------------------- /docs/examples/validation/images/arc-geom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/examples/validation/images/arc-geom.png -------------------------------------------------------------------------------- /docs/examples/validation/images/arc-mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/examples/validation/images/arc-mesh.png -------------------------------------------------------------------------------- /docs/examples/validation/images/channel-geom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/examples/validation/images/channel-geom.png -------------------------------------------------------------------------------- /docs/examples/validation/images/channel-mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/examples/validation/images/channel-mesh.png -------------------------------------------------------------------------------- /docs/examples/validation/images/comp-geom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/examples/validation/images/comp-geom.png -------------------------------------------------------------------------------- /docs/examples/validation/images/comp-mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/docs/examples/validation/images/comp-mesh.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: _static/logo-light-mode.png 2 | :class: only-light 3 | 4 | .. image:: _static/logo-dark-mode.png 5 | :class: only-dark 6 | 7 | .. toctree:: 8 | :hidden: 9 | 10 | installation 11 | user_guide 12 | examples 13 | api 14 | 15 | .. toctree:: 16 | :caption: Development 17 | :hidden: 18 | 19 | contributing 20 | Code of Conduct 21 | License 22 | Changelog 23 | 24 | Documentation 25 | ============= 26 | 27 | ``sectionproperties`` is a python package for the analysis of arbitrary 28 | cross-sections using the finite element method. ``sectionproperties`` 29 | can be used to determine section properties to be used in structural 30 | design and visualise cross-sectional stresses resulting from 31 | combinations of applied forces and bending moments. 32 | 33 | `Subscribe `_ to the ``sectionproperties`` mailing list! 34 | 35 | Installation 36 | ------------ 37 | 38 | You can install ``sectionproperties`` via `pip `_ from 39 | `PyPI `_: 40 | 41 | .. code:: shell 42 | 43 | pip install sectionproperties 44 | 45 | See :ref:`label-installation` for more information. 46 | 47 | Features 48 | -------- 49 | 50 | See the complete list of ``sectionproperties`` features :ref:`here`. 51 | 52 | Contributing 53 | ------------ 54 | 55 | Contributions are very welcome. To learn more, see the 56 | :ref:`Contributor Guide`. 57 | 58 | License 59 | ------- 60 | 61 | Distributed under the terms of the :doc:`MIT License ` ``sectionproperties`` 62 | is free and open source software. 63 | 64 | Support 65 | ------- 66 | 67 | Found a bug 🐛, or have a feature request ✨, raise an issue on the 68 | GitHub `issue 69 | tracker `_. 70 | Alternatively you can get support on the 71 | `discussions `_ 72 | page. 73 | 74 | Disclaimer 75 | ---------- 76 | 77 | ``sectionproperties`` is an open source engineering tool that continues to benefit from 78 | the collaboration of many contributors. Although efforts have been made to ensure the 79 | that relevant engineering theories have been correctly implemented, it remains the 80 | user's responsibility to confirm and accept the output. Refer to the 81 | :doc:`License ` for clarification of the conditions of use. 82 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. _label-installation: 2 | 3 | Installation 4 | ============ 5 | 6 | These instructions will get you a copy of ``sectionproperties`` up and running on your 7 | machine. You will need a working copy of python 3.11, 3.12 or 3.13 to get started. 8 | 9 | Installing ``sectionproperties`` 10 | -------------------------------- 11 | 12 | ``sectionproperties`` uses `shapely `_ to prepare 13 | the cross-section geometry and `CyTriangle `_ to 14 | efficiently generate a conforming triangular mesh. 15 | `numpy `_ and `scipy `_ 16 | are used to aid finite element computations, while 17 | `matplotlib `_ and 18 | `rich `_ are used for post-processing. 19 | 20 | ``sectionproperties`` and all of its dependencies can be installed through the python 21 | package index: 22 | 23 | .. code-block:: shell 24 | 25 | pip install sectionproperties 26 | 27 | Installing ``Numba`` 28 | -------------------- 29 | 30 | ``Numba`` translates a subset of Python and NumPy code into fast machine code, allowing 31 | algorithms to approach the speeds of C. The speed of several ``sectionproperties`` 32 | analysis functions have been enhanced with `numba `_. 33 | To take advantage of this increase in performance you can install ``numba`` alongside 34 | ``sectionproperties`` with: 35 | 36 | .. code-block:: shell 37 | 38 | pip install sectionproperties[numba] 39 | 40 | Installing ``PARDISO`` Solver 41 | ----------------------------- 42 | 43 | The default sparse solver used in ``scipy`` is ``SuperLU``. 44 | It performs okay for small matrices but appears to be slower for larger matrices. The 45 | ``PARDISO`` solver is a much faster alternative 46 | (see `pypardiso `_), but it requires the 47 | installation of the ``MKL`` library, which takes a lot of disk space. Note that this 48 | library is only available for Linux and Windows systems. 49 | 50 | If you do not have a disk space constraint, you can install the ``PARDISO`` solver with: 51 | 52 | .. code-block:: shell 53 | 54 | pip install sectionproperties[pardiso] 55 | 56 | Installing CAD Modules 57 | ---------------------- 58 | 59 | The dependencies used to import from ``.dxf`` and ``.3dm`` (rhino) files are not 60 | included by default in the base installation. 61 | `cad-to-shapely `_ is used to import 62 | ``.dxf`` files, while 63 | `rhino-shapely-interop `_ is 64 | used to import ``.3dm`` files. 65 | 66 | To install ``sectionproperties`` with the above functionality, use the ``dxf`` and/or 67 | ``rhino`` options: 68 | 69 | .. code-block:: shell 70 | 71 | pip install sectionproperties[dxf] 72 | pip install sectionproperties[rhino] 73 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | .. include:: ../LICENSE 5 | :literal: 6 | -------------------------------------------------------------------------------- /docs/user_guide.rst: -------------------------------------------------------------------------------- 1 | User Guide 2 | ========== 3 | 4 | The ``sectionproperties`` user guide provides an overview of each step in the 5 | ``sectionproperties`` workflow, details the theoretical background for the analysis and 6 | includes a section on validation. 7 | 8 | .. toctree:: 9 | :caption: Contents 10 | :maxdepth: 1 11 | 12 | user_guide/overview 13 | user_guide/materials 14 | user_guide/geometry 15 | user_guide/meshing 16 | user_guide/analysis 17 | user_guide/results 18 | user_guide/theory 19 | user_guide/validation 20 | 21 | .. _label-features: 22 | 23 | Features 24 | -------- 25 | 26 | Pre-Processor 27 | ^^^^^^^^^^^^^ 28 | 29 | * ☑ Python API 30 | * ☑ Geometry manipulation by Shapely 31 | * ☑ Common section geometry functions 32 | * ☑ Custom section geometry input 33 | * ☑ Rhino .3dm import 34 | * ☑ .dxf import 35 | * ☑ Perimeter offset tool 36 | * ☑ Quadratic triangular mesh generation 37 | * ☑ Composite material definition 38 | 39 | Cross-Section Analysis 40 | ^^^^^^^^^^^^^^^^^^^^^^ 41 | 42 | * ☑ Global axis geometric section properties 43 | 44 | * ☑ Area 45 | * ☑ Perimeter 46 | * ☑ Mass 47 | * ☑ First moments of area 48 | * ☑ Second moments of area 49 | * ☑ Elastic centroid 50 | 51 | * ☑ Centroidal axis geometric section properties 52 | 53 | * ☑ Second moments of area 54 | * ☑ Elastic section moduli 55 | * ☑ Yield moment 56 | * ☑ Radii of gyration 57 | * ☑ Plastic centroid 58 | * ☑ Plastic section moduli 59 | * ☑ Shape factors 60 | 61 | * ☑ Principal axis geometric section properties 62 | 63 | * ☑ Second moments of area 64 | * ☑ Elastic section moduli 65 | * ☑ Yield moment 66 | * ☑ Radii of gyration 67 | * ☑ Plastic centroid 68 | * ☑ Plastic section moduli 69 | * ☑ Shape factors 70 | 71 | * ☑ Warping section properties 72 | 73 | * ☑ Torsion constant 74 | * ☑ Warping constant 75 | * ☑ Monosymmetry constants 76 | 77 | * ☑ Shear section properties 78 | 79 | * ☑ Shear centre (elastic method) 80 | * ☑ Shear centre (Trefftz's method) 81 | * ☑ Shear areas (global axis) 82 | * ☑ Shear areas (principal axis) 83 | 84 | * ☑ Cross-section stress analysis 85 | * ☑ Mohr's circles for stresses at a point 86 | 87 | Solver 88 | ^^^^^^ 89 | 90 | * ☑ Direct solver 91 | * ☑ CGS iterative solver 92 | * ☑ Sparse matrices 93 | 94 | Post-Processor 95 | ^^^^^^^^^^^^^^ 96 | 97 | * ☑ Plot geometry 98 | * ☑ Plot mesh 99 | * ☑ Plot centroids 100 | * ☑ Plot cross-section stresses 101 | * ☑ Retrieve cross-section stresses 102 | * ☐ Generate cross-section report 103 | -------------------------------------------------------------------------------- /docs/user_guide/analysis.rst: -------------------------------------------------------------------------------- 1 | .. _label-analysis: 2 | 3 | Analysis 4 | ======== 5 | 6 | Section Object 7 | -------------- 8 | 9 | The first step in running a cross-section analysis in ``sectionproperties`` involves the 10 | creation of a :class:`~sectionproperties.analysis.section.Section` object. This object 11 | stores the cross-section geometry and finite element mesh, providing methods to perform 12 | various types of cross-sectional analyses. 13 | 14 | .. automethod:: sectionproperties.analysis.section.Section.__init__ 15 | :noindex: 16 | 17 | Checking Mesh Quality 18 | --------------------- 19 | 20 | Before carrying out a section analysis it is a good idea to check the quality 21 | of the finite element mesh. Some useful methods are provided to display mesh statistics 22 | and to plot the finite element mesh: 23 | 24 | .. automethod:: sectionproperties.analysis.section.Section.display_mesh_info 25 | :noindex: 26 | 27 | .. automethod:: sectionproperties.analysis.section.Section.plot_mesh 28 | :noindex: 29 | 30 | Geometric Analysis 31 | ------------------ 32 | 33 | A geometric analysis calculates the area properties of the section. 34 | 35 | .. automethod:: sectionproperties.analysis.section.Section.calculate_geometric_properties 36 | :noindex: 37 | 38 | Plastic Analysis 39 | ---------------- 40 | 41 | A plastic analysis calculates the plastic properties of the section. 42 | 43 | .. warning:: The plastic analysis in *sectionproperties* assumes all materials are 44 | able to reach their yield stress defined in the material properties. Care should be 45 | taken if analysing materials or cross-sections exhibiting non-linear behaviour, e.g. 46 | reinforced concrete or non-compact steel sections. 47 | 48 | .. automethod:: sectionproperties.analysis.section.Section.calculate_plastic_properties 49 | :noindex: 50 | 51 | Warping Analysis 52 | ---------------- 53 | 54 | A warping analysis calculates the torsion and shear properties of the section. 55 | 56 | .. warning:: There must be connectivity between all elements of the mesh to perform a 57 | valid warping analysis. This is a limitation of the elastic theory that this 58 | implementation is based on, as there is no way to quantify the transfer of shear and 59 | warping between two unconnected regions. 60 | 61 | .. automethod:: sectionproperties.analysis.section.Section.calculate_warping_properties 62 | :noindex: 63 | 64 | Frame Analysis 65 | -------------- 66 | 67 | Calculates the section properties required for a 2D or 3D frame analysis. 68 | 69 | .. note:: This method is significantly faster than performing a geometric and 70 | a warping analysis and has no prerequisites. 71 | 72 | .. automethod:: sectionproperties.analysis.section.Section.calculate_frame_properties 73 | :noindex: 74 | 75 | Stress Analysis 76 | --------------- 77 | 78 | A stress analysis calculates the section stresses arising from a set of forces 79 | and moments. Executing this method returns a 80 | :class:`~sectionproperties.post.stress_post.StressPost` object, which stores the 81 | section stresses and provides stress plotting methods. 82 | 83 | .. warning:: The stress analysis in *sectionproperties* is linear-elastic and does not 84 | account for the yielding of materials or any non-linearities. 85 | 86 | .. automethod:: sectionproperties.analysis.section.Section.calculate_stress 87 | :noindex: 88 | -------------------------------------------------------------------------------- /docs/user_guide/materials.rst: -------------------------------------------------------------------------------- 1 | .. _label-materials: 2 | 3 | Materials 4 | ========= 5 | 6 | Assigning materials to :class:`~sectionproperties.pre.geometry.Geometry` objects is 7 | completely optional in ``sectionproperties``. In fact, if you are not conducting a 8 | composite analysis it is recommended to not specify material properties as this adds 9 | little value to the analysis results. 10 | 11 | If undertaking a composite analysis, materials can be created using the 12 | :class:`~sectionproperties.pre.pre.Material` object: 13 | 14 | .. autoclass:: sectionproperties.pre.pre.Material 15 | :noindex: 16 | 17 | :class:`~sectionproperties.pre.pre.Material` objects are assigned to 18 | :class:`~sectionproperties.pre.geometry.Geometry` objects, learn more about how to 19 | manipulate a :class:`~sectionproperties.pre.geometry.Geometry`'s material here, 20 | :ref:`label-geom-material`. 21 | 22 | Assigning materials affects the results reported by ``sectionproperties``, learn more 23 | :ref:`here`. 24 | -------------------------------------------------------------------------------- /docs/user_guide/meshing.rst: -------------------------------------------------------------------------------- 1 | Meshing 2 | ======= 3 | 4 | A finite element mesh is required to perform a cross-section analysis. After a geometry 5 | has been created, a finite element mesh can then be created for the geometry by using 6 | the :meth:`sectionproperties.pre.geometry.Geometry.create_mesh` or 7 | :meth:`sectionproperties.pre.geometry.CompoundGeometry.create_mesh` methods: 8 | 9 | .. automethod:: sectionproperties.pre.geometry.Geometry.create_mesh 10 | :noindex: 11 | 12 | .. automethod:: sectionproperties.pre.geometry.CompoundGeometry.create_mesh 13 | :noindex: 14 | 15 | .. warning:: 16 | 17 | The length of ``mesh_sizes`` must match the number of regions in the geometry object. 18 | 19 | Once the mesh has been created, it is stored within the geometry object and the geometry 20 | object can then be passed to :class:`~sectionproperties.analysis.section.Section` for 21 | analysis. 22 | 23 | Mesh quality analysis, such as plotting the mesh and displaying mesh metrics, can be 24 | performed using the :class:`~sectionproperties.analysis.section.Section` class. Please 25 | see :ref:`label-analysis` for further information on performing analyses. 26 | -------------------------------------------------------------------------------- /docs/user_guide/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | The process of performing a cross-section analysis with ``sectionproperties`` can be 5 | broken down into three stages: 6 | 7 | 1. **Pre-Processor**: The input geometry, materials and finite element mesh is created. 8 | 2. **Solver**: The cross-section properties are determined. 9 | 3. **Post-Processor**: The results are presented in a number of different formats. 10 | 11 | Pre-Processor 12 | ------------- 13 | 14 | The shape of the cross-section and corresponding materials define the *geometry* of the 15 | cross-section. There are many different ways to create geometry in 16 | ``sectionproperties``, more information can be found in :ref:`label-geometry`. 17 | 18 | The final stage in the pre-processor involves generating a finite element mesh of 19 | the *geometry* that the solver can use to calculate the cross-section properties. 20 | This can easily be performed using the 21 | :func:`~sectionproperties.pre.geometry.Geometry.create_mesh` method that all 22 | :class:`~sectionproperties.pre.geometry.Geometry` objects have access to. 23 | 24 | The following example creates a geometry object with a circular cross-section. The 25 | diameter of the circle is 50 mm and 64 points are used to discretise the circumference 26 | of the circle. A finite element mesh is generated with a maximum triangular area of 27 | 2.5 mm\ :sup:`2` and the geometry is plotted. 28 | 29 | .. plot:: 30 | :include-source: True 31 | :caption: Circular Section 32 | 33 | from sectionproperties.pre.library import circular_section 34 | 35 | geom = circular_section(d=50, n=64) 36 | geom.create_mesh(mesh_sizes=[2.5]) 37 | geom.plot_geometry() 38 | 39 | If you are analysing a composite section, or would like to include material properties 40 | in your model, material properties can be created using the 41 | :class:`~sectionproperties.pre.pre.Material` class. The following example creates a 42 | steel-timber composite section and plots the mesh. 43 | 44 | .. plot:: 45 | :include-source: True 46 | :caption: Steel-Timber Composite Section 47 | 48 | from sectionproperties.pre import Material 49 | from sectionproperties.pre.library import rectangular_section, channel_section 50 | from sectionproperties.analysis import Section 51 | 52 | # create materials 53 | steel = Material( 54 | name="Steel", 55 | elastic_modulus=200e3, 56 | poissons_ratio=0.3, 57 | density=7.85e-6, 58 | yield_strength=500, 59 | color="grey", 60 | ) 61 | timber = Material( 62 | name="Timber", 63 | elastic_modulus=8e3, 64 | poissons_ratio=0.35, 65 | density=6.5e-7, 66 | yield_strength=20, 67 | color="burlywood", 68 | ) 69 | 70 | # create individual geometry objects 71 | pfc = channel_section(d=250, b=90, t_f=15, t_w=8, r=12, n_r=8, material=steel) 72 | rect = rectangular_section(d=350, b=120, material=timber) 73 | pfc_right = pfc.align_center(align_to=rect).align_to(other=rect, on="right") 74 | pfc_left = pfc_right.mirror_section(axis="y", mirror_point=(60, 0)) 75 | 76 | # combine into single geometry and mesh 77 | geom = rect + pfc_left + pfc_right 78 | geom.create_mesh(mesh_sizes=[50.0, 10.0, 10.0]) 79 | 80 | # create section object and plot the mesh 81 | sec = Section(geometry=geom) 82 | sec.plot_mesh() 83 | 84 | Solver 85 | ------ 86 | 87 | The solver operates on a :class:`~sectionproperties.analysis.section.Section` object and 88 | can perform five different analysis types: 89 | 90 | - **Geometric Analysis**: calculates area properties, 91 | :meth:`~sectionproperties.analysis.section.Section.calculate_geometric_properties`. 92 | - **Warping Analysis**: calculates torsion and shear properties, 93 | :meth:`~sectionproperties.analysis.section.Section.calculate_warping_properties`. 94 | - **Frame Analysis**: calculates section properties used for frame analysis (more 95 | efficient than running a geometric and warping analysis), 96 | :meth:`~sectionproperties.analysis.section.Section.calculate_frame_properties`. 97 | - **Plastic Analysis**: calculates plastic properties, 98 | :meth:`~sectionproperties.analysis.section.Section.calculate_plastic_properties`. 99 | - **Stress Analysis**: calculates cross-section stresses, 100 | :meth:`~sectionproperties.analysis.section.Section.calculate_stress`. 101 | 102 | Post-Processor 103 | -------------- 104 | 105 | There are a number of built-in methods to enable the post-processing of analysis 106 | results. For example, a full list of calculated section properties can be printed to the 107 | terminal by using the 108 | :meth:`~sectionproperties.analysis.section.Section.display_results` method. 109 | Alternatively, specific properties can be retrieved by calling the appropriate ``get`` 110 | method, e.g. :meth:`~sectionproperties.analysis.section.Section.get_ic`. 111 | 112 | The calculated cross-section centroids can be plotted by calling the 113 | :meth:`~sectionproperties.analysis.section.Section.plot_centroids` method. The 114 | following example plots the centroids of a 200 PFC section: 115 | 116 | .. plot:: 117 | :include-source: True 118 | :caption: 200 PFC elastic, plastic and shear centroids 119 | 120 | from sectionproperties.pre.library import channel_section 121 | from sectionproperties.analysis import Section 122 | 123 | geom = channel_section(d=200, b=75, t_f=12, t_w=6, r=12, n_r=8) 124 | geom.create_mesh(mesh_sizes=[5.0]) 125 | 126 | sec = Section(geom) 127 | sec.calculate_geometric_properties() 128 | sec.calculate_plastic_properties() 129 | sec.calculate_warping_properties() 130 | 131 | sec.plot_centroids() 132 | 133 | Finally, cross-section stresses may be retrieved by at specific points by calling the 134 | :meth:`~sectionproperties.analysis.section.Section.get_stress_at_points` method, or 135 | plotted by calling the 136 | :meth:`~sectionproperties.post.stress_post.StressPost.plot_stress` method from a 137 | :class:`~sectionproperties.post.stress_post.StressPost` object, obtained after running 138 | the :meth:`~sectionproperties.analysis.section.Section.calculate_stress` method. The 139 | following example plots the von Mises stress in a 100 x 6 SHS subject to bending, shear 140 | and torsion: 141 | 142 | .. plot:: 143 | :include-source: True 144 | :caption: 100 x 6 SHS von Mises stress 145 | 146 | from sectionproperties.pre.library import rectangular_hollow_section 147 | from sectionproperties.analysis import Section 148 | 149 | geom = rectangular_hollow_section(d=100, b=100, t=6, r_out=15, n_r=8) 150 | geom.create_mesh(mesh_sizes=[5.0]) 151 | 152 | sec = Section(geom) 153 | sec.calculate_geometric_properties() 154 | sec.calculate_warping_properties() 155 | stress = sec.calculate_stress(vx=20e3, mxx=15e6, mzz=15e6) 156 | 157 | stress.plot_stress(stress="vm", cmap="viridis", normalize=False) 158 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sectionproperties" 3 | version = "3.9.0" 4 | description = "A python package for the analysis of arbitrary cross-sections using the finite element method." 5 | readme = "README.md" 6 | license = {file = "LICENSE"} 7 | authors = [ 8 | { name = "Robbie van Leeuwen", email = "robbie.vanleeuwen@gmail.com" } 9 | ] 10 | maintainers = [ 11 | { name = "Robbie van Leeuwen", email = "robbie.vanleeuwen@gmail.com" }, 12 | { name = "Connor Ferster", email = "connorferster@gmail.comm" } 13 | ] 14 | keywords = [ 15 | "cross-section", 16 | "structural-engineering", 17 | "finite-element-analysis", 18 | "computational-mechanics", 19 | ] 20 | classifiers = [ 21 | "Development Status :: 5 - Production/Stable", 22 | "Topic :: Scientific/Engineering", 23 | "Environment :: Console", 24 | "License :: OSI Approved :: MIT License", 25 | "Natural Language :: English", 26 | "Programming Language :: Python :: 3.11", 27 | "Programming Language :: Python :: 3.12", 28 | "Programming Language :: Python :: 3.13", 29 | ] 30 | requires-python = ">=3.11,<3.14" 31 | dependencies = [ 32 | "numpy~=2.2", 33 | "scipy~=1.14", 34 | "matplotlib~=3.9", 35 | "shapely~=2.0", 36 | "cytriangle~=2.0", 37 | "rich[jupyter]~=14.0", 38 | "more-itertools~=10.5", 39 | ] 40 | 41 | [project.urls] 42 | Homepage = "https://sectionproperties.readthedocs.io" 43 | Documentation = "https://sectionproperties.readthedocs.io" 44 | Repository = "https://github.com/robbievanleeuwen/section-properties" 45 | Issues = "https://github.com/robbievanleeuwen/section-properties/issues" 46 | Changelog = "https://github.com/robbievanleeuwen/section-properties/releases" 47 | 48 | [project.optional-dependencies] 49 | numba = [ 50 | "numba>=0.60.0", 51 | ] 52 | rhino = [ 53 | "rhino3dm>=8.17.0", 54 | "rhino-shapley-interop>=0.0.4", 55 | ] 56 | dxf = [ 57 | "cad-to-shapely>=0.3.2", 58 | ] 59 | pardiso = [ 60 | "pypardiso>=0.4.6; platform_system == 'Windows' or platform_system == 'Linux'" 61 | ] 62 | 63 | [dependency-groups] 64 | dev = [ 65 | "ipympl==0.9.7", 66 | "notebook==7.4.1", 67 | "sphinx-autobuild==2024.10.03", 68 | ] 69 | docs = [ 70 | "furo==2024.8.6", 71 | "sphinx==8.1.3", # nbsphinx requies sphinx<8.2 for now 72 | "ipykernel==6.29.5", 73 | "ipython==9.2.0", 74 | "nbsphinx==0.9.7", 75 | "nbconvert==7.16.6", 76 | "sphinx-copybutton==0.5.2", 77 | "sphinxext-opengraph==0.10.0", 78 | ] 79 | lint = [ 80 | "pre-commit==4.2.0", 81 | "pyright==1.1.400", 82 | ] 83 | test = [ 84 | "pytest==8.3.5", 85 | "pytest-benchmark[histogram]==5.1.0", 86 | "pytest-check==2.5.3", 87 | "coverage[toml]==7.8.0", 88 | ] 89 | 90 | [tool.uv] 91 | default-groups = ["dev", "docs", "lint", "test"] 92 | 93 | [tool.pyright] 94 | venvPath = "." 95 | venv = ".venv" 96 | pythonVersion = "3.11" 97 | include = ["src"] 98 | exclude = ["**/__init__.py"] 99 | strict = ["src"] 100 | 101 | [tool.coverage.paths] 102 | source = ["src", "*/site-packages"] 103 | tests = ["tests", "*/tests"] 104 | 105 | [tool.coverage.run] 106 | branch = true 107 | source = ["sectionproperties", "tests"] 108 | omit = ["*/benchmarks/*"] 109 | 110 | [tool.pytest.ini_options] 111 | markers = [ 112 | "benchmark_suite: entire benchmark test suite (select with '-m benchmark_suite')", 113 | "benchmark_geom: geometry benchmark tests (select with '-m benchmark_geom')", 114 | "benchmark_mesh: mesh benchmark tests (select with '-m benchmark_mesh')", 115 | "benchmark_analysis: analysis benchmark tests (select with '-m benchmark_analysis')", 116 | ] 117 | 118 | [build-system] 119 | requires = ["hatchling"] 120 | build-backend = "hatchling.build" 121 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | target-version = "py311" 2 | 3 | [lint] 4 | select = [ 5 | "A", # warn about shadowing built-ins 6 | "B", # flake8-bugbear 7 | "C4", # flake8-comprehensions 8 | "COM", # flake8-commas 9 | "D", # pydocstyle 10 | "DOC", # pydoclint 11 | "E", # pycodestyle errors 12 | "EM", # flake8-errmsg 13 | "F", # pyflakes 14 | "FA", # flake8-future-annotations 15 | "I", # isort 16 | "ICN", # flake8-import-conventions 17 | "ISC", # flake8-implicit-str-concat 18 | "N", # pep8-naming 19 | "NPY", # NumPy-specific rules 20 | "NPY201", # NumPy v2 compatibility checks 21 | "PERF", # Perflint 22 | "PT", # flake8-pytest-style 23 | "RUF", # Ruff-specific 24 | "S", # flake8-bandit 25 | "SIM", # flake8-simplify 26 | "TCH", # flake8-type-checking 27 | "UP", # pyupgrade 28 | "W", # pycodestyle warnings 29 | ] 30 | ignore = [ 31 | # recommended ignores per ruff 32 | "W191", # tab-indentation 33 | "E111", # indentation-with-invalid-multiple 34 | "E114", # indentation-with-invalid-multiple-comment 35 | "E117", # over-indented 36 | "D206", # indent-with-spaces 37 | "D300", # triple-single-quotes 38 | "COM812", # missing-trailing-comma 39 | "COM819", # prohibited-trailing-comma 40 | "ISC001", # single-line-implicit-string-concatenation 41 | "ISC002", # multi-line-implicit-string-concatenation 42 | # others 43 | "E741", # ambiguous-variable-name 44 | ] 45 | 46 | [lint.per-file-ignores] 47 | "tests/*" = ["S101"] 48 | "__init__.py*" = ["F401"] 49 | "typings/*" = ["N801", "N803"] 50 | 51 | [lint.pydocstyle] 52 | convention = "google" 53 | 54 | [format] 55 | docstring-code-format = true 56 | -------------------------------------------------------------------------------- /src/sectionproperties/__init__.py: -------------------------------------------------------------------------------- 1 | """sectionproperties. 2 | 3 | A python package for the analysis of arbitrary cross-sections using the 4 | finite element method written by Robbie van Leeuwen. 5 | 6 | sectionproperties can be used to determine section properties to be used 7 | in structural design and visualise cross-sectional stresses resulting 8 | from combinations of applied forces and bending moments. 9 | """ 10 | -------------------------------------------------------------------------------- /src/sectionproperties/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | """sectionproperties analysis module.""" 2 | 3 | from sectionproperties.analysis.section import Section 4 | -------------------------------------------------------------------------------- /src/sectionproperties/analysis/solver.py: -------------------------------------------------------------------------------- 1 | """Methods used for solving linear systems and displaying info on tasks.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import numpy as np 8 | from rich.progress import ( 9 | BarColumn, 10 | Progress, 11 | ProgressColumn, 12 | SpinnerColumn, 13 | Task, 14 | TextColumn, 15 | ) 16 | from rich.table import Column 17 | from rich.text import Text 18 | from scipy.sparse.linalg import cgs, spsolve 19 | 20 | if TYPE_CHECKING: 21 | import numpy.typing as npt 22 | from scipy.sparse import csc_matrix 23 | from scipy.sparse.linalg import LinearOperator 24 | 25 | 26 | try: 27 | import pypardiso 28 | 29 | sp_solve = pypardiso.spsolve 30 | except ImportError: 31 | sp_solve = spsolve 32 | 33 | 34 | def solve_cgs( 35 | k: csc_matrix, 36 | f: npt.NDArray[np.float64], 37 | m: LinearOperator, 38 | tol: float = 1e-5, 39 | ) -> npt.NDArray[np.float64]: 40 | """Solves a linear system using the CGS iterative method. 41 | 42 | Args: 43 | k: ``N x N`` matrix of the linear system 44 | f: ``N x 1`` right hand side of the linear system 45 | m: Preconditioner for the linear matrix approximating the inverse of ``k`` 46 | tol: Relative tolerance for the solver to achieve. Defaults to ``1e-5``. 47 | 48 | Returns: 49 | The solution vector to the linear system of equations 50 | 51 | Raises: 52 | RuntimeError: If the CGS iterative method does not converge 53 | """ 54 | u, info = cgs(A=k, b=f, rtol=tol, M=m) 55 | 56 | if info != 0: 57 | msg = "CGS iterative method did not converge." 58 | raise RuntimeError(msg) 59 | 60 | return u 61 | 62 | 63 | def solve_cgs_lagrange( 64 | k_lg: csc_matrix, 65 | f: npt.NDArray[np.float64], 66 | m: LinearOperator, 67 | tol: float = 1e-5, 68 | ) -> npt.NDArray[np.float64]: 69 | """Solves a linear system using the CGS iterative method (Lagrangian multiplier). 70 | 71 | Args: 72 | k_lg: ``(N+1) x (N+1)`` Lagrangian multiplier matrix of the linear system 73 | f: ``N x 1`` right hand side of the linear system 74 | m: Preconditioner for the linear matrix approximating the inverse of ``k`` 75 | tol: Relative tolerance for the solver to achieve. Defaults to ``1e-5``. 76 | 77 | Returns: 78 | The solution vector to the linear system of equations 79 | 80 | Raises: 81 | RuntimeError: If the CGS iterative method does not converge or the error from 82 | the Lagrangian multiplier method exceeds the tolerance 83 | """ 84 | u, info = cgs(A=k_lg, b=np.append(f, 0), rtol=tol, M=m) 85 | 86 | if info != 0: 87 | msg = "CGS iterative method did not converge." 88 | raise RuntimeError(msg) 89 | 90 | # compute error 91 | err = u[-1] / max(np.absolute(u)) 92 | 93 | if err > tol: 94 | msg = "Lagrangian multiplier method error exceeds tolerance." 95 | raise RuntimeError(msg) 96 | 97 | return u[:-1] 98 | 99 | 100 | def solve_direct( 101 | k: csc_matrix, 102 | f: npt.NDArray[np.float64], 103 | ) -> npt.NDArray[np.float64]: 104 | """Solves a linear system using the direct solver method. 105 | 106 | Args: 107 | k: ``N x N`` matrix of the linear system 108 | f: ``N x 1`` right hand side of the linear system 109 | 110 | Returns: 111 | The solution vector to the linear system of equations 112 | """ 113 | return sp_solve(A=k, b=f) 114 | 115 | 116 | def solve_direct_lagrange( 117 | k_lg: csc_matrix, 118 | f: npt.NDArray[np.float64], 119 | ) -> npt.NDArray[np.float64]: 120 | """Solves a linear system using the direct solver method (Lagrangian multiplier). 121 | 122 | Args: 123 | k_lg: ``(N+1) x (N+1)`` Lagrangian multiplier matrix of the linear system 124 | f: ``N x 1`` right hand side of the linear system 125 | 126 | Returns: 127 | The solution vector to the linear system of equations 128 | 129 | Raises: 130 | RuntimeError: If the Lagrangian multiplier method exceeds a relative tolerance 131 | of ``1e-7`` or absolute tolerance related to your machine's floating point 132 | precision. 133 | """ 134 | u = sp_solve(A=k_lg, b=np.append(f, 0)) 135 | 136 | # compute error 137 | multiplier = abs(u[-1]) 138 | rel_error = multiplier / max(np.absolute(u)) 139 | 140 | if rel_error > 1e-7 and multiplier > 10.0 * np.finfo(float).eps: 141 | msg = "Lagrangian multiplier method error exceeds the prescribed tolerance, " 142 | msg += "consider refining your mesh. If this error is unexpected raise an " 143 | msg += "issue at https://github.com/robbievanleeuwen/section-properties/issues." 144 | raise RuntimeError(msg) 145 | 146 | return u[:-1] 147 | 148 | 149 | class CustomTimeElapsedColumn(ProgressColumn): 150 | """Renders time elapsed in milliseconds.""" 151 | 152 | def render( 153 | self, 154 | task: Task, 155 | ) -> Text: 156 | """Show time remaining. 157 | 158 | Args: 159 | task: Rich progress task 160 | 161 | Returns: 162 | Rich text object 163 | """ 164 | elapsed = task.finished_time if task.finished else task.elapsed 165 | 166 | if elapsed is None: 167 | return Text("-:--:--", style="progress.elapsed") 168 | 169 | elapsed_string = f"[ {elapsed:.4f} s ]" 170 | 171 | return Text(elapsed_string, style="progress.elapsed") 172 | 173 | 174 | def create_progress() -> Progress: 175 | """Returns a Rich Progress class. 176 | 177 | Returns: 178 | Rich Progress class containing a spinner, progress description, percentage and 179 | time 180 | """ 181 | return Progress( 182 | SpinnerColumn(), 183 | TextColumn( 184 | "[progress.description]{task.description}", table_column=Column(ratio=1) 185 | ), 186 | BarColumn(bar_width=None, table_column=Column(ratio=1)), 187 | TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), 188 | CustomTimeElapsedColumn(), 189 | expand=True, 190 | ) 191 | -------------------------------------------------------------------------------- /src/sectionproperties/post/__init__.py: -------------------------------------------------------------------------------- 1 | """sectionproperties post-processor.""" 2 | -------------------------------------------------------------------------------- /src/sectionproperties/pre/__init__.py: -------------------------------------------------------------------------------- 1 | """sectionproperties pre-processor.""" 2 | 3 | from sectionproperties.pre.geometry import CompoundGeometry, Geometry 4 | from sectionproperties.pre.pre import Material 5 | -------------------------------------------------------------------------------- /src/sectionproperties/pre/bisect_section.py: -------------------------------------------------------------------------------- 1 | """A number of functions required to bisect shapely geometries.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from shapely import GeometryCollection, LineString, Polygon 8 | 9 | if TYPE_CHECKING: 10 | import numpy as np 11 | import numpy.typing as npt 12 | 13 | 14 | def create_line_segment( 15 | point_on_line: tuple[float, float] | npt.NDArray[np.float64], 16 | vector: npt.NDArray[np.float64], 17 | bounds: tuple[float, float, float, float], 18 | ) -> LineString: 19 | """Create a line segment given a point, vector and bounds. 20 | 21 | Return a LineString of a line that contains ``point_on_line`` in the direction 22 | of ``vector`` bounded by ``bounds``. 23 | 24 | Args: 25 | point_on_line: Point on line 26 | vector: Line direction 27 | bounds: Min and max ordinates of geometry 28 | 29 | Returns: 30 | Line string defined as per docstring 31 | """ 32 | p_x, p_y = point_on_line 33 | b_2 = max(bounds) 34 | b_1 = min(bounds) 35 | 36 | if abs(vector[0]) > 1e-12: # Not a vertical line 37 | scale_factor_2 = (b_2 - p_x) / vector[0] 38 | y_2 = scale_factor_2 * vector[1] + p_y 39 | scale_factor_1 = (b_1 - p_x) / vector[0] 40 | y_1 = scale_factor_1 * vector[1] + p_y 41 | 42 | return LineString([(b_1, y_1), (b_2, y_2)]) 43 | else: # Vertical line 44 | scale_factor_2 = (b_2 - p_y) / vector[1] 45 | x_2 = scale_factor_2 * vector[0] + p_x 46 | scale_factor_1 = (b_1 - p_y) / vector[1] 47 | x_1 = scale_factor_1 * vector[0] + p_x 48 | 49 | return LineString([(x_1, b_1), (x_2, b_2)]) 50 | 51 | 52 | def group_top_and_bottom_polys( 53 | polys: GeometryCollection, 54 | line: LineString, 55 | ) -> tuple[list[Polygon], list[Polygon]]: 56 | """Sort polygons into groups above and below a line. 57 | 58 | Returns a tuple of two lists representing the list of polygons in ``polys`` on 59 | the "top" side of ``line`` and the list of polygons on the "bottom" side of the 60 | ``line`` after the original geometry has been split by ``line``. 61 | 62 | The 0th tuple element is the "top" polygons and the 1st element is the "bottom" 63 | polygons. 64 | 65 | In the event that ``line`` is a perfectly vertical line, the "top" polys are the 66 | polygons on the "right" of the ``line`` and the "bottom" polys are the polygons on 67 | the "left" of the ``line``. 68 | 69 | Args: 70 | polys: Collection of polygons to sort 71 | line: Line about which polygons have been split 72 | 73 | Raises: 74 | RuntimeError: If the split operation does not generate polygons 75 | 76 | Returns: 77 | List of polygons above and below the line 78 | """ 79 | top_acc: list[Polygon] = [] 80 | bot_acc: list[Polygon] = [] 81 | 82 | for poly in polys.geoms: 83 | if not isinstance(poly, Polygon): 84 | msg = "Geometry split error." 85 | raise RuntimeError(msg) 86 | 87 | m, b = line_mx_plus_b(line) 88 | px, py = poly.representative_point().coords[0] 89 | 90 | if b is not None: # Not a vertical line (special case) 91 | y_test = m * px + b 92 | if py < y_test: 93 | bot_acc.append(poly) 94 | elif py > y_test: 95 | top_acc.append(poly) 96 | 97 | else: # The special case of vertical line 98 | lx, _ = line.coords[0] 99 | if px < lx: 100 | bot_acc.append(poly) 101 | elif px > lx: 102 | top_acc.append(poly) 103 | 104 | return top_acc, bot_acc 105 | 106 | 107 | def line_mx_plus_b( 108 | line: LineString, 109 | ) -> tuple[float, float | None]: 110 | """Get the slope and y-intercept of a line. 111 | 112 | Returns a tuple representing the values of "m" and "b" from the definition of 113 | ``line`` as "y = mx + b". 114 | 115 | Args: 116 | line: Line to define 117 | 118 | Returns: 119 | Gradient and y-intercept of line, ``(1, None)`` if vertical 120 | """ 121 | y2, y1 = line.coords[1][1], line.coords[0][1] 122 | x2, x1 = line.coords[1][0], line.coords[0][0] 123 | 124 | if x2 - x1 == 0: 125 | return (1, None) 126 | 127 | m_slope = (y2 - y1) / (x2 - x1) 128 | point_on_line = line.coords[0] 129 | p_x, p_y = point_on_line 130 | 131 | # solve line eqn for b given a known point on the line 132 | b_intercept = p_y - m_slope * p_x 133 | 134 | return (m_slope, b_intercept) 135 | -------------------------------------------------------------------------------- /src/sectionproperties/pre/library/__init__.py: -------------------------------------------------------------------------------- 1 | """sectionproperties section library.""" 2 | 3 | from sectionproperties.pre.library.bridge_sections import ( 4 | i_girder_section, 5 | super_t_girder_section, 6 | ) 7 | from sectionproperties.pre.library.concrete_sections import ( 8 | add_bar, 9 | cee_wall, 10 | concrete_circular_section, 11 | concrete_column_section, 12 | concrete_rectangular_section, 13 | concrete_tee_section, 14 | double_lift_core_a, 15 | double_lift_core_b, 16 | rectangular_wall, 17 | single_lift_core, 18 | stairwell, 19 | tee_wall, 20 | ) 21 | from sectionproperties.pre.library.nastran_sections import ( 22 | nastran_bar, 23 | nastran_box, 24 | nastran_box1, 25 | nastran_chan, 26 | nastran_chan1, 27 | nastran_chan2, 28 | nastran_cross, 29 | nastran_dbox, 30 | nastran_fcross, 31 | nastran_gbox, 32 | nastran_h, 33 | nastran_hat, 34 | nastran_hat1, 35 | nastran_hexa, 36 | nastran_i, 37 | nastran_i1, 38 | nastran_l, 39 | nastran_rod, 40 | nastran_tee, 41 | nastran_tee1, 42 | nastran_tee2, 43 | nastran_tube, 44 | nastran_tube2, 45 | nastran_zed, 46 | ) 47 | from sectionproperties.pre.library.primitive_sections import ( 48 | circular_section, 49 | circular_section_by_area, 50 | cruciform_section, 51 | elliptical_section, 52 | rectangular_section, 53 | triangular_radius_section, 54 | triangular_section, 55 | ) 56 | from sectionproperties.pre.library.steel_sections import ( 57 | angle_section, 58 | box_girder_section, 59 | bulb_section, 60 | cee_section, 61 | channel_section, 62 | circular_hollow_section, 63 | elliptical_hollow_section, 64 | i_section, 65 | mono_i_section, 66 | polygon_hollow_section, 67 | rectangular_hollow_section, 68 | tapered_flange_channel, 69 | tapered_flange_i_section, 70 | tee_section, 71 | zed_section, 72 | ) 73 | from sectionproperties.pre.library.timber_sections import clt_rectangular_section 74 | -------------------------------------------------------------------------------- /src/sectionproperties/pre/library/timber_sections.py: -------------------------------------------------------------------------------- 1 | """Timber sections library.""" 2 | 3 | from __future__ import annotations 4 | 5 | import sectionproperties.pre.geometry as geometry 6 | import sectionproperties.pre.library.primitive_sections as primitive_sections 7 | import sectionproperties.pre.pre as pre 8 | 9 | 10 | def clt_rectangular_section( 11 | d: list[float], 12 | layer_mat: list[pre.Material], 13 | b: float, 14 | ) -> geometry.CompoundGeometry: 15 | """Constructs a CLT rectangular section. 16 | 17 | Constructs a CLT rectangular section, with layer depths ``d``, layer materials 18 | ``layer_mat``, and width ``b``. 19 | 20 | Args: 21 | d: CLT layer section thickness 22 | layer_mat: A list of timber materials for each layer (from top to bottom) 23 | b: CLT section width 24 | 25 | Returns: 26 | CLT rectangular section geometry 27 | 28 | Example: 29 | The following example creates a 120mm CLT cross-section: 30 | 31 | .. plot:: 32 | :include-source: True 33 | :caption: 120mm CLT section geometry 34 | 35 | from sectionproperties.pre import Material 36 | from sectionproperties.pre.library import clt_rectangular_section 37 | from sectionproperties.analysis import Section 38 | 39 | timber0 = Material( 40 | name="Timber0", 41 | elastic_modulus=9.5e3, 42 | poissons_ratio=0.35, 43 | density=4.4e-7, 44 | yield_strength=5.5, 45 | color="burlywood", 46 | ) 47 | timber90 = Material( 48 | name="Timber90", 49 | elastic_modulus=317, 50 | poissons_ratio=0.35, 51 | density=4.4e-7, 52 | yield_strength=5.5, 53 | color="orange", 54 | ) 55 | 56 | geom = clt_rectangular_section( 57 | d=[40, 40, 40], 58 | layer_mat=[timber0, timber90, timber0], 59 | b=1000 60 | ) 61 | 62 | geom.create_mesh(mesh_sizes=[0]) # a size of zero creates a coarse mesh 63 | Section(geometry=geom).plot_mesh() 64 | """ 65 | layer_geom: list[geometry.Geometry] = [] 66 | for idx in range(len(d)): 67 | di = float(d[idx]) 68 | layer = layer_mat[idx] 69 | 70 | timb_mat = layer 71 | 72 | # create rectangular timber geometry 73 | layer = primitive_sections.rectangular_section(d=di, b=b, material=timb_mat) 74 | offset = -d[idx] * (idx + 1) 75 | layer = layer.shift_section(y_offset=offset) 76 | 77 | layer_geom.append(layer) 78 | 79 | # create compound geometry 80 | return geometry.CompoundGeometry(geoms=layer_geom) 81 | -------------------------------------------------------------------------------- /src/sectionproperties/pre/library/utils.py: -------------------------------------------------------------------------------- 1 | """Utilities used in the sections library.""" 2 | 3 | from __future__ import annotations 4 | 5 | import numpy as np 6 | 7 | 8 | def draw_radius( 9 | pt: tuple[float, float], 10 | r: float, 11 | theta: float, 12 | n: int, 13 | ccw: bool = True, 14 | phi: float = np.pi * 0.5, 15 | ) -> list[tuple[float, float]]: 16 | """Generates a list of points describing an arc. 17 | 18 | Generates an arc centered at point ``pt``, with radius ``r``, starting at angle 19 | ``theta``, with *n* points. If ``r=0``, adds ``pt`` only. ``phi`` describes the 20 | angle of rotation e.g. ``pi / 2`` is a quarter circle. 21 | 22 | Args: 23 | pt: Centre of radius (``x``, ``y``) 24 | r: Radius 25 | theta: Initial angle in radians 26 | n: Number of points 27 | ccw: If True, counter-clockwise rotation. Defaults to ``True``. 28 | phi: Angle describing radius extent in radians. Defaults to ``np.pi * 0.5``. 29 | 30 | Returns: 31 | List of points 32 | """ 33 | points: list[tuple[float, float]] = [] 34 | 35 | if r == 0: 36 | points.append(pt) 37 | return points 38 | 39 | mult = 1 if ccw else -1 40 | 41 | # calculate radius of points 42 | for i in range(n): 43 | # determine angle 44 | t = theta + mult * i * 1.0 / max(1, n - 1) * phi 45 | 46 | x = pt[0] + r * np.cos(t) 47 | y = pt[1] + r * np.sin(t) 48 | 49 | points.append((x, y)) 50 | 51 | return points 52 | 53 | 54 | def rotate( 55 | point: tuple[float, float], 56 | angle: float, 57 | ) -> tuple[float, float]: 58 | """Rotates a point counterclockwise by a given angle around origin ``(0, 0)``. 59 | 60 | Args: 61 | point: Point coordinates to be rotated 62 | angle: Angle to rotate point coordinates in radians 63 | 64 | Returns: 65 | Coordinates of rotated point 66 | """ 67 | pt_x, pt_y = point 68 | 69 | c = np.cos(angle) 70 | s = np.sin(angle) 71 | 72 | new_x = c * pt_x - s * pt_y 73 | new_y = s * pt_x + c * pt_y 74 | 75 | return new_x, new_y 76 | -------------------------------------------------------------------------------- /src/sectionproperties/pre/pre.py: -------------------------------------------------------------------------------- 1 | """Classes and methods for generic pre-procesing in sectionproperties.""" 2 | 3 | from __future__ import annotations 4 | 5 | from dataclasses import dataclass 6 | from typing import Any 7 | 8 | import cytriangle as triangle 9 | 10 | 11 | @dataclass(eq=True, frozen=True) 12 | class Material: 13 | """Class for structural materials. 14 | 15 | Provides a way of storing material properties related to a specific material. The 16 | color can be a multitude of different formats, refer to 17 | https://matplotlib.org/stable/api/colors_api.html and 18 | https://matplotlib.org/stable/gallery/color/named_colors.html for more information. 19 | 20 | Attributes: 21 | name: Material name 22 | elastic_modulus: Material modulus of elasticity 23 | poissons_ratio: Material Poisson's ratio 24 | yield_strength: Material yield strength 25 | density: Material density (mass per unit volume) 26 | color: Material color for rendering 27 | 28 | Example: 29 | The following example creates materials for concrete, steel and timber:: 30 | 31 | from sectionproperties.pre import Material 32 | 33 | concrete = Material( 34 | name="Concrete", 35 | elastic_modulus=30.1e3, 36 | poissons_ratio=0.2, 37 | density=2.4e-6, 38 | yield_strength=32, 39 | color="lightgrey", 40 | ) 41 | steel = Material( 42 | name="Steel", 43 | elastic_modulus=200e3, 44 | poissons_ratio=0.3, 45 | density=7.85e-6, 46 | yield_strength=500, 47 | color="grey", 48 | ) 49 | timber = Material( 50 | name="Timber", 51 | elastic_modulus=8e3, 52 | poissons_ratio=0.35, 53 | density=6.5e-7, 54 | yield_strength=20, 55 | color="burlywood", 56 | ) 57 | """ 58 | 59 | name: str 60 | elastic_modulus: float 61 | poissons_ratio: float 62 | yield_strength: float 63 | density: float 64 | color: str 65 | 66 | @property 67 | def shear_modulus(self) -> float: 68 | """Returns the shear modulus of the material. 69 | 70 | Material shear modulus, derived from the elastic modulus and Poisson's ratio 71 | assuming an isotropic material. 72 | 73 | Returns: 74 | Shear modulus of the material 75 | """ 76 | return self.elastic_modulus / (2 * (1 + self.poissons_ratio)) 77 | 78 | 79 | DEFAULT_MATERIAL = Material("default", 1, 0, 1, 1, "w") 80 | 81 | 82 | def create_mesh( 83 | points: list[tuple[float, float]], 84 | facets: list[tuple[int, int]], 85 | holes: list[tuple[float, float]], 86 | control_points: list[tuple[float, float]], 87 | mesh_sizes: list[float] | float, 88 | min_angle: float, 89 | coarse: bool, 90 | ) -> dict[str, list[list[float]] | list[list[int]]]: 91 | """Generates a triangular mesh. 92 | 93 | Creates a quadratic triangular mesh using the ``CyTriangle`` module, which utilises 94 | the code ``Triangle``, by Jonathan Shewchuk. 95 | 96 | Args: 97 | points: List of points (``x``, ``y``) defining the vertices of the cross-section 98 | facets: List of point index pairs (``p1``, ``p2``) defining the edges of the 99 | cross-section 100 | holes: List of points (``x``, ``y``) defining the locations of holes within the 101 | cross-section. If there are no holes, provide an empty list []. 102 | control_points: A list of points (``x``, ``y``) that define different regions of 103 | the cross-section. A control point is an arbitrary point within a region 104 | enclosed by facets. 105 | mesh_sizes: List of maximum element areas for each region defined by a control 106 | point 107 | min_angle: The meshing algorithm adds vertices to the mesh to ensure that no 108 | angle smaller than the minimum angle (in degrees, rounded to 1 decimal 109 | place). Note that small angles between input segments cannot be eliminated. 110 | If the minimum angle is 20.7 deg or smaller, the triangulation algorithm is 111 | theoretically guaranteed to terminate (given sufficient precision). The 112 | algorithm often doesn't terminate for angles greater than 33 deg. Some 113 | meshes may require angles well below 20 deg to avoid problems associated 114 | with insufficient floating-point precision. 115 | coarse: If set to True, will create a coarse mesh (no area or quality 116 | constraints) 117 | 118 | Returns: 119 | Dictionary containing mesh data 120 | """ 121 | if not isinstance(mesh_sizes, list): 122 | mesh_sizes = [mesh_sizes] 123 | 124 | tri: dict[str, Any] = {} # create tri dictionary 125 | tri["vertices"] = points # set point 126 | tri["segments"] = facets # set facets 127 | 128 | if holes: 129 | tri["holes"] = holes # set holes 130 | 131 | # prepare regions 132 | regions: list[list[float | int]] = [] 133 | 134 | for i, cp in enumerate(control_points): 135 | rg = [cp[0], cp[1], i, mesh_sizes[i]] 136 | regions.append(rg) 137 | 138 | tri["regions"] = regions # set regions 139 | 140 | # generate mesh 141 | if coarse: 142 | mesh = triangle.triangulate(tri, "pAo2") 143 | else: 144 | mesh = triangle.triangulate(tri, f"pq{min_angle:.1f}Aao2") 145 | 146 | return mesh 147 | -------------------------------------------------------------------------------- /src/sectionproperties/pre/rhino.py: -------------------------------------------------------------------------------- 1 | """Methods to load rhino files, interfacing with rhino-shapely-interop.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING, Any 6 | 7 | from rhino_shapely_interop.importers import RhImporter 8 | 9 | if TYPE_CHECKING: 10 | import pathlib 11 | 12 | from shapely import Polygon 13 | 14 | 15 | def load_3dm( 16 | r3dm_filepath: pathlib.Path | str, 17 | **kwargs: Any, 18 | ) -> list[Polygon]: 19 | """Load a Rhino ``.3dm`` file and import the single surface planer breps. 20 | 21 | Args: 22 | r3dm_filepath: File path to the rhino ``.3dm`` file. 23 | kwargs: See below. 24 | 25 | Keyword Args: 26 | refine_num (Optional[int]): Bézier curve interpolation number. In Rhino a 27 | surface's edges are nurb based curves. Shapely does not support nurbs, so 28 | the individual Bézier curves are interpolated using straight lines. This 29 | parameter sets the number of straight lines used in the interpolation. 30 | Default is 1. 31 | vec1 (Optional[numpy.ndarray]): A 3d vector in the Shapely plane. Rhino is a 3D 32 | geometry environment. Shapely is a 2D geometric library. Thus a 2D plane 33 | needs to be defined in Rhino that represents the Shapely coordinate system. 34 | ``vec1`` represents the 1st vector of this plane. It will be used as 35 | Shapely's x direction. Default is [1,0,0]. 36 | vec2 (Optional[numpy.ndarray]): Continuing from ``vec1``, ``vec2`` is another 37 | vector to define the Shapely plane. It must not be [0,0,0] and it's only 38 | requirement is that it is any vector in the Shapely plane (but not equal to 39 | ``vec1``). Default is [0,1,0]. 40 | plane_distance (Optional[float]): The distance to the Shapely plane. Default is 41 | 0. 42 | project (Optional[bool]): Controls if the breps are projected onto the plane in 43 | the direction of the Shapley plane's normal. Default is True. 44 | parallel (Optional[bool]): Controls if only the rhino surfaces that have the 45 | same normal as the Shapely plane are yielded. If true, all non parallel 46 | surfaces are filtered out. Default is False. 47 | 48 | Raises: 49 | RuntimeError: A RuntimeError is raised if no polygons are found in the file. 50 | This is dependent on the keyword arguments. Try adjusting the keyword 51 | arguments if this error is raised. 52 | 53 | Returns: 54 | List of Polygons found in the file. 55 | """ 56 | rhi = RhImporter.from_file(str(r3dm_filepath)) 57 | list_polygons = list(rhi.get_planer_brep(**kwargs)) 58 | 59 | if len(list_polygons) == 0: 60 | msg = "No shapely.Polygon objects found. Consider adjusting the keyword " 61 | msg += f"arguments. File name: {r3dm_filepath}." 62 | raise RuntimeError(msg) 63 | 64 | return list_polygons 65 | 66 | 67 | def load_brep_encoding( 68 | brep: str, 69 | **kwargs: Any, 70 | ) -> list[Polygon]: 71 | """Load an encoded single surface planer brep. 72 | 73 | Args: 74 | brep: Rhino3dm.Brep encoded as a string. 75 | kwargs: See below. 76 | 77 | Keyword Args: 78 | refine_num (Optional[int]): Bézier curve interpolation number. In Rhino a 79 | surface's edges are nurb based curves. Shapely does not support nurbs, so 80 | the individual Bézier curves are interpolated using straight lines. This 81 | parameter sets the number of straight lines used in the interpolation. 82 | Default is 1. 83 | vec1 (Optional[numpy.ndarray]): A 3d vector in the Shapely plane. Rhino is a 3D 84 | geometry environment. Shapely is a 2D geometric library. Thus a 2D plane 85 | needs to be defined in Rhino that represents the Shapely coordinate system. 86 | ``vec1`` represents the 1st vector of this plane. It will be used as 87 | Shapely's x direction. Default is [1,0,0]. 88 | vec2 (Optional[numpy.ndarray]): Continuing from ``vec1``, ``vec2`` is another 89 | vector to define the Shapely plane. It must not be [0,0,0] and it's only 90 | requirement is that it is any vector in the Shapely plane (but not equal to 91 | ``vec1``). Default is [0,1,0]. 92 | plane_distance (Optional[float]): The distance to the Shapely plane. Default is 93 | 0. 94 | project (Optional[bool]): Controls if the breps are projected onto the plane in 95 | the direction of the Shapley plane's normal. Default is True. 96 | parallel (Optional[bool]): Controls if only the rhino surfaces that have the 97 | same normal as the Shapely plane are yielded. If true, all non parallel 98 | surfaces are filtered out. Default is False. 99 | 100 | Raises: 101 | RuntimeError: A RuntimeError is raised if no polygons are found in the encoding. 102 | This is dependent on the keyword arguments. Try adjusting the keyword 103 | arguments if this error is raised. 104 | 105 | Returns: 106 | The Polygons found in the encoding string. 107 | """ 108 | rhi = RhImporter.from_serialzed_brep(brep) 109 | geom = list(rhi.get_planer_brep(**kwargs)) 110 | 111 | if len(geom) == 0: 112 | msg = "No shapely.Polygon objects found for encoded object." 113 | raise RuntimeError(msg) 114 | 115 | return geom 116 | -------------------------------------------------------------------------------- /src/sectionproperties/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/src/sectionproperties/py.typed -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Test suite for the sectionproperties package.""" 2 | -------------------------------------------------------------------------------- /tests/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | """Analysis tests for sectionproperties.""" 2 | -------------------------------------------------------------------------------- /tests/analysis/test_stress.py: -------------------------------------------------------------------------------- 1 | """Tests for cross-section stress calculation.""" 2 | 3 | from __future__ import annotations 4 | 5 | import math 6 | 7 | import pytest 8 | 9 | import sectionproperties.pre.library.primitive_sections as primitive_sections 10 | from sectionproperties.analysis.section import Section 11 | 12 | # define geometry 13 | rect = primitive_sections.rectangular_section(b=50, d=100) 14 | rect.create_mesh(mesh_sizes=0) # coarse mesh 15 | sec = Section(geometry=rect) 16 | 17 | 18 | # test stress runtime errors 19 | def test_stress_runtime_errors(): 20 | """Tests for raising RuntimeErrors.""" 21 | # check runtime error with no analysis performed 22 | with pytest.raises(RuntimeError): 23 | sec.calculate_stress() 24 | 25 | sec.calculate_geometric_properties() 26 | 27 | # check runtime errors with shear/torsion applied, no warping analysis 28 | with pytest.raises(RuntimeError): 29 | sec.calculate_stress(vx=1) 30 | 31 | with pytest.raises(RuntimeError): 32 | sec.get_stress_at_points(pts=[(10, 10)], vx=1) 33 | 34 | with pytest.raises(RuntimeError): 35 | sec.calculate_stress(vy=1) 36 | 37 | with pytest.raises(RuntimeError): 38 | sec.get_stress_at_points(pts=[(10, 10)], vy=1) 39 | 40 | with pytest.raises(RuntimeError): 41 | sec.calculate_stress(mzz=1) 42 | 43 | with pytest.raises(RuntimeError): 44 | sec.get_stress_at_points(pts=[(10, 10)], mzz=1) 45 | 46 | # check no runtime errors with no shear/torsion applied 47 | sec.calculate_stress(n=1) 48 | sec.calculate_stress(mxx=1) 49 | sec.calculate_stress(myy=1) 50 | sec.calculate_stress(m11=1) 51 | sec.calculate_stress(m22=1) 52 | 53 | sec.calculate_warping_properties() 54 | 55 | # check no runtime errors after warping analysis 56 | sec.calculate_stress(n=1) 57 | sec.calculate_stress(mxx=1) 58 | sec.calculate_stress(myy=1) 59 | sec.calculate_stress(m11=1) 60 | sec.calculate_stress(m22=1) 61 | sec.calculate_stress(vx=1) 62 | sec.calculate_stress(vy=1) 63 | sec.calculate_stress(mzz=1) 64 | sec.get_stress_at_points(pts=[(10, 10)], mzz=1) 65 | 66 | 67 | def test_rectangle(): 68 | """Tests bending stresses for a rectangle.""" 69 | mxx = 7 70 | sy = 50.0 * 100.0**2 / 6.0 71 | sig_max = mxx / sy 72 | sig_0, sig_1, sig_2 = sec.get_stress_at_points( 73 | pts=[(25, 50), (25, 75), (25, 100)], mxx=mxx 74 | ) 75 | assert sig_0 == pytest.approx((0, 0, 0)) 76 | assert sig_1 == pytest.approx((sig_max / 2.0, 0, 0)) 77 | assert sig_2 == pytest.approx((sig_max, 0, 0)) 78 | 79 | 80 | def test_rotated_rectangle(): 81 | """Tests bending stresses for a rotated rectangle.""" 82 | b = 50 83 | d = 100 84 | angle = math.atan(100 / 50) 85 | cx = b / 2 * math.cos(angle) - d / 2 * math.sin(angle) 86 | cy = b / 2 * math.sin(angle) + d / 2 * math.cos(angle) 87 | sy = b * d / 6.0 * cy 88 | mxx = 7 89 | sig_max = mxx / sy 90 | rot_rect = ( 91 | primitive_sections.rectangular_section(b=b, d=d) 92 | .shift_section(x_offset=-b / 2, y_offset=-d / 2) 93 | .rotate_section(angle=angle, use_radians=True) 94 | ) 95 | rot_rect.create_mesh(mesh_sizes=0) # coarse mesh 96 | rot_sec = Section(geometry=rot_rect) 97 | rot_sec.calculate_geometric_properties() 98 | rot_sec.calculate_warping_properties() 99 | _, sig_1 = rot_sec.get_stress_at_points(pts=[(cx, 0), (cx, cy)], mxx=mxx) 100 | assert sig_1 == pytest.approx((sig_max, 0, 0)) 101 | -------------------------------------------------------------------------------- /tests/benchmarks/__init__.py: -------------------------------------------------------------------------------- 1 | """Benchmark tests for sectionproperties.""" 2 | -------------------------------------------------------------------------------- /tests/benchmarks/conftest.py: -------------------------------------------------------------------------------- 1 | """pytest benchmark configurations.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import pytest 8 | 9 | from sectionproperties.pre import Geometry, Material 10 | from sectionproperties.pre.library import ( 11 | circular_hollow_section, 12 | circular_section, 13 | concrete_column_section, 14 | rectangular_section, 15 | ) 16 | 17 | if TYPE_CHECKING: 18 | from collections.abc import Callable 19 | 20 | 21 | @pytest.fixture 22 | def concrete() -> Material: 23 | """Creates a concrete material object. 24 | 25 | Returns: 26 | Concrete 27 | """ 28 | return Material( 29 | name="Concrete", 30 | elastic_modulus=30.1e3, 31 | poissons_ratio=0.2, 32 | yield_strength=32, 33 | density=2.4e-6, 34 | color="lightgrey", 35 | ) 36 | 37 | 38 | @pytest.fixture 39 | def steel() -> Material: 40 | """Creates a steel material object. 41 | 42 | Returns: 43 | Steel 44 | """ 45 | return Material( 46 | name="Steel", 47 | elastic_modulus=200e3, 48 | poissons_ratio=0.3, 49 | yield_strength=500, 50 | density=7.85e-6, 51 | color="grey", 52 | ) 53 | 54 | 55 | @pytest.fixture 56 | def rect_geom() -> Geometry: 57 | """Creates a rectangular geometry. 58 | 59 | Returns: 60 | Geometry 61 | """ 62 | return rectangular_section(d=100, b=50) 63 | 64 | 65 | @pytest.fixture 66 | def chs_geom() -> Geometry: 67 | """Creates a rectangular geometry. 68 | 69 | Returns: 70 | Geometry 71 | """ 72 | return circular_hollow_section(d=100, t=3, n=128) 73 | 74 | 75 | @pytest.fixture 76 | def concrete_column_with_hole(concrete, steel) -> Callable: 77 | """Creates a concrete column with a hole at its centre. 78 | 79 | Args: 80 | concrete: Concrete material 81 | steel: Steel material 82 | 83 | Returns: 84 | Generator function 85 | """ 86 | 87 | def _generate_geom() -> Geometry: 88 | geom = concrete_column_section( 89 | d=600, 90 | b=300, 91 | dia_bar=25, 92 | area_bar=500, 93 | n_x=3, 94 | n_y=6, 95 | cover=35, 96 | n_circle=4, 97 | filled=False, 98 | conc_mat=concrete, 99 | steel_mat=steel, 100 | ) 101 | hole = circular_section(d=100, n=32).shift_section(x_offset=150, y_offset=300) 102 | 103 | return geom - hole 104 | 105 | return _generate_geom 106 | 107 | 108 | @pytest.fixture 109 | def analysis_geometry() -> Callable: 110 | """Create a geometry to be used for analysis. 111 | 112 | Returns: 113 | Generator function 114 | """ 115 | 116 | def _generate_geom(num_elements: int) -> Geometry: 117 | mat_a = Material("a", 1, 0, 1, 1, color="b") 118 | mat_b = Material("b", 10, 0, 1, 1, color="g") 119 | mat_c = Material("c", 5, 0, 1, 1, color="r") 120 | mat_d = Material("d", 2, 0, 1, 1, color="y") 121 | 122 | a = rectangular_section(20, 20, mat_a) 123 | b = rectangular_section(20, 20, mat_b).align_to(a, "right") 124 | c = rectangular_section(20, 20, mat_c).align_to(a, "top") 125 | d = rectangular_section(20, 20, mat_d).align_to(a, "top").align_to(a, "right") 126 | geom = a + b + c + d 127 | mesh_area = geom.calculate_area() / num_elements * 1.6 128 | return geom.create_mesh([mesh_area]) 129 | 130 | return _generate_geom 131 | -------------------------------------------------------------------------------- /tests/benchmarks/test_benchmark_analysis.py: -------------------------------------------------------------------------------- 1 | """Benchmark tests for sectionproperties analysis.""" 2 | 3 | import pytest 4 | 5 | from sectionproperties.analysis import Section 6 | 7 | 8 | @pytest.mark.benchmark_suite 9 | @pytest.mark.benchmark_analysis 10 | @pytest.mark.parametrize("elements", [50, 500, 5000]) 11 | def test_create_section(benchmark, analysis_geometry, elements): 12 | """Benchmark test for creating a Section object.""" 13 | geom = analysis_geometry(elements) 14 | 15 | def create_section(): 16 | Section(geometry=geom) 17 | 18 | benchmark(create_section) 19 | 20 | 21 | @pytest.mark.benchmark_suite 22 | @pytest.mark.benchmark_analysis 23 | @pytest.mark.parametrize("elements", [50, 500, 5000]) 24 | def test_geometric_analysis(benchmark, analysis_geometry, elements): 25 | """Benchmark test for conducting a geometric analysis.""" 26 | geom = analysis_geometry(elements) 27 | sec = Section(geometry=geom) 28 | 29 | def geometric_analysis(): 30 | sec.calculate_geometric_properties() 31 | 32 | benchmark(geometric_analysis) 33 | 34 | 35 | @pytest.mark.benchmark_suite 36 | @pytest.mark.benchmark_analysis 37 | def test_plastic_analysis(benchmark, analysis_geometry): 38 | """Benchmark test for conducting a plastic analysis. 39 | 40 | Note that a plastic analysis is mesh-independent. 41 | """ 42 | geom = analysis_geometry(1) 43 | sec = Section(geometry=geom) 44 | sec.calculate_geometric_properties() 45 | 46 | def plastic_analysis(): 47 | sec.calculate_plastic_properties() 48 | 49 | benchmark(plastic_analysis) 50 | 51 | 52 | @pytest.mark.benchmark_suite 53 | @pytest.mark.benchmark_analysis 54 | @pytest.mark.parametrize("elements", [50, 500, 5000]) 55 | def test_warping_analysis(benchmark, analysis_geometry, elements): 56 | """Benchmark test for conducting a warping analysis.""" 57 | geom = analysis_geometry(elements) 58 | sec = Section(geometry=geom) 59 | sec.calculate_geometric_properties() 60 | 61 | def warping_analysis(): 62 | sec.calculate_warping_properties() 63 | 64 | benchmark(warping_analysis) 65 | 66 | 67 | @pytest.mark.benchmark_suite 68 | @pytest.mark.benchmark_analysis 69 | @pytest.mark.parametrize("elements", [50, 500, 5000]) 70 | def test_frame_analysis(benchmark, analysis_geometry, elements): 71 | """Benchmark test for conducting a frame analysis.""" 72 | geom = analysis_geometry(elements) 73 | sec = Section(geometry=geom) 74 | 75 | def frame_analysis(): 76 | sec.calculate_frame_properties() 77 | 78 | benchmark(frame_analysis) 79 | 80 | 81 | @pytest.mark.benchmark_suite 82 | @pytest.mark.benchmark_analysis 83 | @pytest.mark.parametrize("elements", [50, 500, 5000]) 84 | def test_stress_analysis(benchmark, analysis_geometry, elements): 85 | """Benchmark test for conducting a stress analysis.""" 86 | geom = analysis_geometry(elements) 87 | sec = Section(geometry=geom) 88 | sec.calculate_geometric_properties() 89 | sec.calculate_warping_properties() 90 | 91 | def stress_analysis(): 92 | sec.calculate_stress(n=1, vx=1, vy=1, mxx=1, m22=1, mzz=1) 93 | 94 | benchmark(stress_analysis) 95 | -------------------------------------------------------------------------------- /tests/benchmarks/test_benchmark_geom.py: -------------------------------------------------------------------------------- 1 | """Benchmark tests for sectionproperties geometry creation.""" 2 | 3 | import pytest 4 | 5 | from sectionproperties.pre.library import circular_hollow_section, rectangular_section 6 | 7 | 8 | @pytest.mark.benchmark_suite 9 | @pytest.mark.benchmark_geom 10 | def test_create_simple_geometry(benchmark): 11 | """Benchmark test for creating rectangular geometry.""" 12 | benchmark(rectangular_section, d=100, b=50) 13 | 14 | 15 | @pytest.mark.benchmark_suite 16 | @pytest.mark.benchmark_geom 17 | def test_create_intermediate_geometry(benchmark): 18 | """Benchmark test for creating CHS geometry.""" 19 | benchmark(circular_hollow_section, d=100, t=3, n=128) 20 | 21 | 22 | @pytest.mark.benchmark_suite 23 | @pytest.mark.benchmark_geom 24 | def test_create_complex_geometry(benchmark, concrete_column_with_hole): 25 | """Benchmark test for creating concrete geometry.""" 26 | benchmark(concrete_column_with_hole) 27 | -------------------------------------------------------------------------------- /tests/benchmarks/test_benchmark_mesh.py: -------------------------------------------------------------------------------- 1 | """Benchmark tests for sectionproperties mesh creation.""" 2 | 3 | import pytest 4 | 5 | 6 | @pytest.mark.benchmark_suite 7 | @pytest.mark.benchmark_mesh 8 | @pytest.mark.parametrize("ms", [0.0, 50.0, 5.0]) 9 | def test_create_simple_mesh(benchmark, rect_geom, ms): 10 | """Benchmark test for creating a mesh for a rectangular geometry.""" 11 | geom = rect_geom 12 | benchmark(geom.create_mesh, ms) 13 | 14 | 15 | @pytest.mark.benchmark_suite 16 | @pytest.mark.benchmark_mesh 17 | @pytest.mark.parametrize("ms", [0.0, 1.0, 0.3]) 18 | def test_create_intermediate_mesh(benchmark, chs_geom, ms): 19 | """Benchmark test for creating a mesh for a CHS geometry.""" 20 | geom = chs_geom 21 | benchmark(geom.create_mesh, ms) 22 | 23 | 24 | @pytest.mark.benchmark_suite 25 | @pytest.mark.benchmark_mesh 26 | @pytest.mark.parametrize("ms", [0.0, 100.0, 20.0]) 27 | def test_create_complex_mesh(benchmark, concrete_column_with_hole, ms): 28 | """Benchmark test for creating a mesh for a concrete geometry.""" 29 | geom = concrete_column_with_hole() 30 | benchmark(geom.create_mesh, ms) 31 | -------------------------------------------------------------------------------- /tests/geometry/3in x 2in.3dm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/tests/geometry/3in x 2in.3dm -------------------------------------------------------------------------------- /tests/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | """Geometry tests for sectionproperties.""" 2 | -------------------------------------------------------------------------------- /tests/geometry/complex_shape.3dm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/tests/geometry/complex_shape.3dm -------------------------------------------------------------------------------- /tests/geometry/compound_shape.3dm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbievanleeuwen/section-properties/5896d0e8083fbba8ac2ed248fb710ef4ea271dd7/tests/geometry/compound_shape.3dm -------------------------------------------------------------------------------- /tests/geometry/rhino_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 10000, 3 | "archive3dm": 60, 4 | "opennurbs": -1910998424, 5 | "data": "+n8CAAUMAAAAAAAA+/8CABQAAAAAAAAAxdu1YGDm0xG/5AAQgwEi8JhRycz8/wIAzQsAAAAAAAAzAIAAQEEDAAAAAAAAEAQAAAABAAAA+n8CAL4AAAAAAAAA+/8CABQAAAAAAAAA3dTXTkfp0xG/5QAQgwEi8BDyeHD8/wIAhgAAAAAAAAARAgAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAJzGZuv9/AoAAAAAAAAAAAAEAAAD6fwIAvgAAAAAAAAD7/wIAFAAAAAAAAADd1NdOR+nTEb/lABCDASLwEPJ4cPz/AgCGAAAAAAAAABECAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAADwPwIAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAZ0+Yh/38CgAAAAAAAAAAAAQAAAPp/AgC+AAAAAAAAAPv/AgAUAAAAAAAAAN3U105H6dMRv+UAEIMBIvAQ8nhw/P8CAIYAAAAAAAAAEQIAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAPA/AgAAAAAAAAAAAPA/AAAAAAAA8D8AAAAAAAAAAAAAAAAAAPA/AOZUmFb/fwKAAAAAAAAAAAABAAAA+n8CAL4AAAAAAAAA+/8CABQAAAAAAAAA3dTXTkfp0xG/5QAQgwEi8BDyeHD8/wIAhgAAAAAAAAARAgAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAA8D8CAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAA2Lbnzf9/AoAAAAAAAAAAADuG1gUAgABATQIAAAAAAAAQBAAAAAEAAAD6fwIAgQAAAAAAAAD7/wIAFAAAAAAAAADb1NdOR+nTEb/lABCDASLw9JvpI/z/AgBJAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AwAAABT68D7/fwKAAAAAAAAAAAABAAAA+n8CAIEAAAAAAAAA+/8CABQAAAAAAAAA29TXTkfp0xG/5QAQgwEi8PSb6SP8/wIASQAAAAAAAAAQAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwMAAAAT+VQy/38CgAAAAAAAAAAAAQAAAPp/AgCBAAAAAAAAAPv/AgAUAAAAAAAAANvU105H6dMRv+UAEIMBIvD0m+kj/P8CAEkAAAAAAAAAEAAAAAAAAPA/AAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8DAAAACicWQv9/AoAAAAAAAAAAAAEAAAD6fwIAgQAAAAAAAAD7/wIAFAAAAAAAAADb1NdOR+nTEb/lABCDASLw9JvpI/z/AgBJAAAAAAAAABAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AwAAAA0ksk7/fwKAAAAAAAAAAAA7htYFAIAAQBYBAAAAAAAAEAEAAAABAAAA+n8CAP0AAAAAAAAA+/8CABQAAAAAAAAA39TXTkfp0xG/5QAQgwEi8HPX2Pf8/wIAxQAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAIAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAPA/0P8rhv9/AoAAAAAAAAAAACQx3z4AgABAyQAAAAAAAAAQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAADwPwAAAAAAAPA/AAAAAAAAAAACAAAAAQAAAAIAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAACAAAAAgAAAAMAAAAAAAAAAAAAABvksB8AgABAGQEAAAAAAAAQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwEAAAACAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8CAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AgAAAAMAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwMAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8DAAAAAAAAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/iuMUrQCAAEAZAgAAAAAAABAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAABAAAAAAAAAAEAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/AQAAAAEAAAAAAAAAAAAAAAAAAAAAAPA/AQAAAAEAAAACAAAAAAAAAAEAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/AgAAAAIAAAAAAAAAAAAAAAAAAAAAAPA/AgAAAAIAAAADAAAAAAAAAAEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/AwAAAAMAAAAAAAAAAAAAAAAAAAAAAPA/AwAAAAMAAAAAAAAAAAAAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSHTOevfjl/9IdM569+OX/I/TaqgCAAEApAAAAAAAAABABAAAAAAAAAAQAAAAAAAAAAQAAAAIAAAADAAAAAQAAAAAAAABzz7mMAIAAQDEAAAAAAAAAEQEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUfCC4wAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAAAAAAAAAAAAAAAAACAAEAFAAAAAAAAAACN7wLSAIAAQAUAAAAAAAAAAI3vAtIAAAAAAIAAQA0AAAAAAAAAAQAAAAEAAAAAXSkSzLgALVz/fwKAAAAAAAAAAAA=" 6 | } 7 | -------------------------------------------------------------------------------- /tests/geometry/test_perimeter.py: -------------------------------------------------------------------------------- 1 | """Tests perimeter calculations.""" 2 | 3 | from __future__ import annotations 4 | 5 | import numpy as np 6 | import pytest_check as check 7 | 8 | import sectionproperties.pre.library.primitive_sections as sections 9 | import sectionproperties.pre.library.steel_sections as steel_sections 10 | from sectionproperties.analysis.section import Section 11 | from sectionproperties.pre.geometry import Geometry 12 | 13 | r_tol = 1e-3 14 | 15 | 16 | def test_rectangular_perimeter(): 17 | """Test perimeter of a rectangle.""" 18 | rect = sections.rectangular_section(d=500, b=300) 19 | rect.create_mesh(mesh_sizes=[200]) 20 | section = Section(geometry=rect) 21 | section.calculate_geometric_properties() 22 | assert section.get_perimeter() == 2 * (500 + 300) 23 | 24 | 25 | def test_i_section(): 26 | """Test perimeter of an i-section.""" 27 | i_section = steel_sections.i_section( 28 | d=308, 29 | b=305, 30 | t_f=15.4, 31 | t_w=9.9, 32 | r=16.5, 33 | n_r=16, 34 | ) 35 | i_section.create_mesh(mesh_sizes=[100]) 36 | section = Section(geometry=i_section) 37 | section.calculate_geometric_properties() 38 | perim = ( 39 | (2 * 305) 40 | + (4 * 15.4) 41 | + 2 * (305 - 9.9 - 2 * 16.5) 42 | + (2 * np.pi * 16.5) 43 | + 2 * (308 - 2 * 15.4 - 2 * 16.5) 44 | ) 45 | check.almost_equal(section.get_perimeter(), perim, rel=r_tol) 46 | 47 | 48 | def test_box_girder_perimeter(): 49 | """Test perimeter of a box girder.""" 50 | box_girder = steel_sections.box_girder_section( 51 | d=400, 52 | b_t=700, 53 | b_b=100, 54 | t_ft=20, 55 | t_fb=20, 56 | t_w=12, 57 | ) 58 | box_girder.create_mesh(mesh_sizes=[100]) 59 | section = Section(geometry=box_girder) 60 | section.calculate_geometric_properties() 61 | assert section.get_perimeter() == 700 + 100 + 2 * 500 62 | 63 | 64 | def test_custom_geometry_perimeter(): 65 | """Test perimeter of custom geometry from points.""" 66 | points = [(0, 0), (5, 0), (11, 8), (3, 2), (0, 2)] 67 | facets = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] 68 | control_points = [(5.0, 5.0)] 69 | custom = Geometry.from_points( 70 | points=points, 71 | facets=facets, 72 | control_points=control_points, 73 | ) 74 | custom.create_mesh(mesh_sizes=[100]) 75 | section = Section(geometry=custom) 76 | section.calculate_geometric_properties() 77 | assert section.get_perimeter() == 5 + 2 + 10 + 10 + 3 78 | 79 | 80 | def test_compound_rectangular_perimeter(): 81 | """Test perimeter of a built up rectangular section.""" 82 | rect1 = sections.rectangular_section(d=100, b=100) 83 | rect2 = sections.rectangular_section(d=50, b=50).align_to(other=rect1, on="top") 84 | rect3 = sections.rectangular_section(d=50, b=100).align_to(other=rect1, on="right") 85 | rect4 = ( 86 | sections.rectangular_section(d=50, b=50) 87 | .align_to(other=rect3, on="bottom") 88 | .align_to(other=rect3, on="right", inner=True) 89 | ) 90 | geom = rect1 + rect2 + rect3 + rect4 91 | geom.create_mesh(mesh_sizes=[100]) 92 | section = Section(geometry=geom) 93 | section.calculate_geometric_properties() 94 | assert section.get_perimeter() == ( 95 | 150 + 50 + 50 + 100 + 100 + 50 + 50 + 50 + 50 + 150 96 | ) 97 | 98 | 99 | def test_compound_rectangular_isection_perimeter1(): 100 | """Test perimeter of a compound i-section - part 1.""" 101 | d = 300 102 | b = 150 103 | tf = 10 104 | tw = 6 105 | r = 12 106 | b_p = 250 107 | t_p = 16 108 | ub = steel_sections.i_section(d=d, b=b, t_f=tf, t_w=tw, r=r, n_r=16) 109 | plate = ( 110 | sections.rectangular_section(b=b_p, d=t_p) 111 | .align_center(align_to=ub) 112 | .align_to(other=ub, on="top") 113 | ) 114 | geom = ub + plate 115 | geom.create_mesh(mesh_sizes=[100]) 116 | section = Section(geometry=geom) 117 | section.calculate_geometric_properties() 118 | perim = ( 119 | b 120 | + (4 * tf) 121 | + 2 * (b - tw - 2 * r) 122 | + (2 * np.pi * r) 123 | + 2 * (d - 2 * tf - 2 * r) 124 | + (b_p - b) 125 | + (2 * t_p) 126 | + b_p 127 | ) 128 | check.almost_equal(section.get_perimeter(), perim, rel=r_tol) 129 | 130 | 131 | def test_compound_rectangular_isection_perimeter2(): 132 | """Test perimeter of a compound i-section - part 2.""" 133 | i_section = steel_sections.i_section( 134 | d=308, 135 | b=305, 136 | t_f=15.4, 137 | t_w=9.9, 138 | r=16.5, 139 | n_r=16, 140 | ) 141 | rect1 = ( 142 | sections.rectangular_section(d=330, b=16) 143 | .align_center(align_to=i_section) 144 | .align_to(other=i_section, on="left") 145 | ) 146 | rect2 = ( 147 | sections.rectangular_section(d=330, b=16) 148 | .align_center(align_to=i_section) 149 | .align_to(other=i_section, on="right") 150 | ) 151 | geom = i_section + rect1 + rect2 152 | geom.create_mesh(mesh_sizes=[100]) 153 | section = Section(geometry=geom) 154 | section.calculate_geometric_properties() 155 | assert section.get_perimeter() == 2 * 330 + 4 * 16 + 2 * 305 + 2 * (330 - 308) 156 | 157 | 158 | def test_compound_rhs_isection_perimeter(): 159 | """Test perimeter of a compound rhs/i-section.""" 160 | d = 200 161 | b = 150 162 | t = 9 163 | r = 15 164 | b_p = 250 165 | t_p = 16 166 | rhs = steel_sections.rectangular_hollow_section(d=d, b=b, t=t, r_out=r, n_r=16) 167 | plate1 = ( 168 | sections.rectangular_section(b=b_p, d=t_p) 169 | .align_center(align_to=rhs) 170 | .align_to(other=rhs, on="top") 171 | ) 172 | plate2 = ( 173 | sections.rectangular_section(b=b_p, d=t_p) 174 | .align_center(align_to=rhs) 175 | .align_to(other=rhs, on="bottom") 176 | ) 177 | geom = rhs + plate1 + plate2 178 | geom.create_mesh(mesh_sizes=[100]) 179 | section = Section(geometry=geom) 180 | section.calculate_geometric_properties() 181 | perim = ( 182 | (2 * b_p) 183 | + (4 * t_p) 184 | + 2 * (b_p - b + 2 * r) 185 | + (2 * np.pi * r) 186 | + 2 * (d - 2 * r) 187 | ) 188 | check.almost_equal(section.get_perimeter(), perim, rel=r_tol) 189 | -------------------------------------------------------------------------------- /tests/post/__init__.py: -------------------------------------------------------------------------------- 1 | """Post-processor tests for sectionproperties.""" 2 | -------------------------------------------------------------------------------- /tests/post/test_post.py: -------------------------------------------------------------------------------- 1 | """Tests for secionpropertiers.post.post.""" 2 | 3 | from __future__ import annotations 4 | 5 | import platform 6 | 7 | import matplotlib.pyplot as plt 8 | import pytest 9 | import pytest_check as check 10 | 11 | from sectionproperties.analysis import Section 12 | from sectionproperties.pre import Material 13 | from sectionproperties.pre.library import rectangular_section 14 | 15 | linux_only = pytest.mark.skipif( 16 | platform.system() != "Linux", 17 | reason="Only test plotting on Linux", 18 | ) 19 | 20 | 21 | @pytest.fixture 22 | def example_section() -> Section: 23 | """Creates an example section with geometric properties. 24 | 25 | Returns: 26 | Section 27 | """ 28 | geom = rectangular_section(d=1, b=1) 29 | geom.create_mesh(mesh_sizes=0) 30 | 31 | return Section(geometry=geom) 32 | 33 | 34 | def test_as_dict(example_section): 35 | """Test as_dict method.""" 36 | sec = example_section 37 | sec.calculate_geometric_properties() 38 | res = sec.section_props.asdict() 39 | 40 | check.almost_equal(res["area"], sec.get_area()) 41 | 42 | 43 | def test_elastic_centroid_error(example_section): 44 | """Test the ValueError when calculating elastic centroids.""" 45 | sec = example_section 46 | 47 | with pytest.raises(RuntimeError, match="Calculate geometric properties first"): 48 | sec.section_props.calculate_elastic_centroid() 49 | 50 | 51 | def test_centroidal_properties_error(example_section): 52 | """Test the ValueError when calculating centroidal_properties.""" 53 | sec = example_section 54 | 55 | with pytest.raises(RuntimeError, match="Calculate geometric properties first"): 56 | sec.section_props.calculate_centroidal_properties( 57 | node_list=sec.mesh["vertices"], 58 | ) 59 | 60 | 61 | @linux_only 62 | def test_save_plot(example_section, tmp_path): 63 | """Tests saving a plot.""" 64 | sec = example_section 65 | d = tmp_path / "sub" 66 | d.mkdir() 67 | 68 | sec.plot_mesh(filename=d / "fig.png") 69 | plt.close("all") 70 | 71 | 72 | @linux_only 73 | def test_supplied_axis(example_section): 74 | """Tests supplying an axis to a plot.""" 75 | sec = example_section 76 | _, ax = plt.subplots() 77 | 78 | sec.plot_mesh(ax=ax, render=False) 79 | plt.close("all") 80 | sec.plot_mesh(nrows=2, axis_index=1, render=False) 81 | plt.close("all") 82 | 83 | with pytest.raises(ValueError, match="is not compatible"): 84 | sec.plot_mesh(nrows=2, ncols=2, axis_index=5, render=False) 85 | 86 | plt.close("all") 87 | 88 | 89 | @linux_only 90 | def test_plot_centroids(example_section): 91 | """Tests plotting centroids.""" 92 | sec = example_section 93 | sec.calculate_geometric_properties() 94 | sec.plot_centroids(render=False) 95 | plt.close("all") 96 | 97 | 98 | def test_print_results(example_section): 99 | """Tests printing results.""" 100 | sec = example_section 101 | sec.display_results() 102 | sec.calculate_geometric_properties() 103 | sec.calculate_warping_properties() 104 | sec.calculate_plastic_properties() 105 | sec.display_results() 106 | 107 | mat = Material("a", 1, 0.2, 2, 1, "k") 108 | geom_mat = rectangular_section(d=1, b=1, material=mat) 109 | geom_mat.create_mesh(mesh_sizes=0) 110 | sec_mat = Section(geometry=geom_mat) 111 | sec_mat.display_results() 112 | sec_mat.calculate_geometric_properties() 113 | sec_mat.calculate_warping_properties() 114 | sec_mat.calculate_plastic_properties() 115 | sec_mat.display_results() 116 | -------------------------------------------------------------------------------- /tests/post/test_stress_post.py: -------------------------------------------------------------------------------- 1 | """Tests for secionpropertiers.post.stress_post.""" 2 | 3 | from __future__ import annotations 4 | 5 | import platform 6 | 7 | import pytest 8 | 9 | from sectionproperties.analysis import Section 10 | from sectionproperties.pre import Material 11 | from sectionproperties.pre.library import rectangular_section 12 | 13 | linux_only = pytest.mark.skipif( 14 | platform.system() != "Linux", 15 | reason="Only test plotting on Linux", 16 | ) 17 | 18 | 19 | @pytest.fixture 20 | def example_section() -> tuple[Section, Material]: 21 | """Creates an example section with geometric properties. 22 | 23 | Returns: 24 | Section and material 25 | """ 26 | mat_a = Material("a", 1, 0.2, 2, 1, "k") 27 | mat_b = Material("b", 2, 0.2, 2, 1, "r") 28 | rect_a = rectangular_section(d=1, b=1, material=mat_a) 29 | rect_b = rectangular_section(d=1, b=1, material=mat_b).align_to(rect_a, "right") 30 | geom = rect_a + rect_b 31 | geom.create_mesh(mesh_sizes=[0]) 32 | 33 | return Section(geometry=geom), mat_b 34 | 35 | 36 | @linux_only 37 | def test_stress_plot(example_section): 38 | """Tests the plot_stress() method.""" 39 | sec, mat_b = example_section 40 | sec.calculate_geometric_properties() 41 | stress = sec.calculate_stress(n=1) 42 | stress.plot_stress(stress="n_zz", render=False) 43 | stress.plot_stress(stress="n_zz", stress_limits=[0, 1], render=False) 44 | stress.plot_stress(stress="n_zz", material_list=[mat_b], render=False) 45 | 46 | 47 | @linux_only 48 | def test_stress_plot_constant(): 49 | """Tests the plot_stress() method with a constant stress.""" 50 | geom = rectangular_section(d=1, b=1) 51 | geom.create_mesh(mesh_sizes=[0]) 52 | sec = Section(geometry=geom) 53 | sec.calculate_geometric_properties() 54 | sec.calculate_stress(n=1).plot_stress(stress="n_zz", render=False) 55 | 56 | 57 | @linux_only 58 | def test_stress_vector_plot(example_section): 59 | """Tests the plot_stress_vector() method.""" 60 | sec, _ = example_section 61 | sec.calculate_geometric_properties() 62 | sec.calculate_warping_properties() 63 | sec.calculate_stress(mzz=1).plot_stress_vector(stress="zxy", render=False) 64 | 65 | 66 | @linux_only 67 | def test_plot_mohrs_circles(example_section): 68 | """Tests the plot_mohrs_circles() method.""" 69 | sec, _ = example_section 70 | sec.calculate_geometric_properties() 71 | stress = sec.calculate_stress(mxx=1) 72 | stress.plot_mohrs_circles(x=1, y=0.5, render=False) 73 | 74 | with pytest.raises(ValueError, match="is not within mesh"): 75 | stress.plot_mohrs_circles(x=2, y=2, render=False) 76 | -------------------------------------------------------------------------------- /tests/section_library/__init__.py: -------------------------------------------------------------------------------- 1 | """Section library tests for sectionproperties.""" 2 | -------------------------------------------------------------------------------- /tests/section_library/test_steel_sections.py: -------------------------------------------------------------------------------- 1 | """Tests for the steel sections library.""" 2 | 3 | from __future__ import annotations 4 | 5 | import warnings 6 | 7 | import pytest 8 | 9 | import sectionproperties.pre.library.steel_sections as ss 10 | from sectionproperties.analysis import Section 11 | 12 | 13 | def test_angle_section_toe_thickness(): 14 | """Tests the angle section when the toe thickness equals the thickness.""" 15 | geom = ss.angle_section(d=20, b=20, t=3, r_r=3.5, r_t=3, n_r=16) 16 | geom.create_mesh(mesh_sizes=1.0) 17 | sec = Section(geom) 18 | 19 | # check for no additional warnings 20 | with pytest.warns() as record: 21 | warnings.warn("user", UserWarning, stacklevel=1) 22 | sec.calculate_geometric_properties() 23 | sec.calculate_warping_properties() 24 | 25 | assert len(record) == 1 26 | -------------------------------------------------------------------------------- /tests/section_library/test_timber_sections.py: -------------------------------------------------------------------------------- 1 | """Tests for the timber sections library.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | import pytest_check as check 7 | 8 | import sectionproperties.pre.library.timber_sections as ts 9 | import sectionproperties.pre.pre as pre 10 | 11 | r_tol = 1e-6 12 | 13 | 14 | # material setup 15 | @pytest.fixture 16 | def get_materials() -> tuple[pre.Material, pre.Material]: 17 | """Creates a timber material parallel and perpendicular-to-grain. 18 | 19 | Returns: 20 | Material objects 21 | """ 22 | timb_mat0 = pre.Material( 23 | name="Timber E0", 24 | elastic_modulus=9.5e3, 25 | poissons_ratio=0.35, 26 | density=4.4e-7, 27 | yield_strength=5.5, 28 | color="burlywood", 29 | ) 30 | 31 | timb_mat90 = pre.Material( 32 | name="Timber90", 33 | elastic_modulus=317, 34 | poissons_ratio=0.35, 35 | density=4.4e-7, 36 | yield_strength=5.5, 37 | color="orange", 38 | ) 39 | 40 | return timb_mat0, timb_mat90 41 | 42 | 43 | def test_timber_clt_rectangular_section(get_materials): 44 | """Tests the timber clt_rectangular_section() method.""" 45 | timber0, timber90 = get_materials 46 | 47 | rect = ts.clt_rectangular_section( 48 | d=[40, 40, 40], layer_mat=[timber0, timber90, timber0], b=1000 49 | ) 50 | 51 | # check geometry is created correctly 52 | timb0_area = 0 53 | timb90_area = 0 54 | 55 | for geom in rect.geoms: 56 | if geom.material == timber0: 57 | timb0_area += geom.calculate_area() 58 | elif geom.material == timber90: 59 | timb90_area += geom.calculate_area() 60 | 61 | actual_timb0_area = 2 * 40 * 1000 62 | actual_timb90_area = 40 * 1000 63 | 64 | # check areas 65 | check.almost_equal(timb0_area, actual_timb0_area) 66 | check.almost_equal(timb90_area, actual_timb90_area) 67 | -------------------------------------------------------------------------------- /tests/validation/__init__.py: -------------------------------------------------------------------------------- 1 | """Validation tests for sectionproperties.""" 2 | -------------------------------------------------------------------------------- /tests/validation/test_angle_validation.py: -------------------------------------------------------------------------------- 1 | """Validation of cross-section properties for an angle section.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | import pytest_check as check 7 | 8 | from sectionproperties.analysis import Section 9 | from sectionproperties.pre.library import angle_section 10 | 11 | # constants 12 | tol = 1e-6 13 | warp_tol = 1e-4 14 | 15 | 16 | @pytest.fixture 17 | def ang_section() -> Section: 18 | """Creates a 150x90x12 unequal angle section with a 5 units square mesh. 19 | 20 | Returns: 21 | Section object 22 | """ 23 | geom = angle_section(d=150, b=90, t=12, r_r=10, r_t=5, n_r=8) 24 | geom.create_mesh(mesh_sizes=5) 25 | sec = Section(geometry=geom) 26 | sec.calculate_geometric_properties() 27 | return sec 28 | 29 | 30 | def test_angle_section_geometric(ang_section): 31 | """Test angle section geometric properties. 32 | 33 | Geometric properties calculated from sectionproperties v3.0.2 with a refined mesh 34 | [mesh_sizes=0.5]. 35 | """ 36 | sec = ang_section 37 | 38 | check.almost_equal(sec.section_props.area, 2.747059e03, rel=tol) 39 | check.almost_equal(sec.section_props.perimeter, 4.713501e02, rel=tol) 40 | check.almost_equal(sec.section_props.mass, 2.747059e03, rel=tol) 41 | check.almost_equal(sec.section_props.ea, 2.747059e03, rel=tol) 42 | check.almost_equal(sec.section_props.qx, 1.400486e05, rel=tol) 43 | check.almost_equal(sec.section_props.qy, 5.830033e04, rel=tol) 44 | check.almost_equal(sec.section_props.ixx_g, 1.342632e07, rel=tol) 45 | check.almost_equal(sec.section_props.iyy_g, 2.955753e06, rel=tol) 46 | check.almost_equal(sec.section_props.ixy_g, 1.086603e06, rel=tol) 47 | check.almost_equal(sec.section_props.cx, 2.122282e01, rel=tol) 48 | check.almost_equal(sec.section_props.cy, 5.098127e01, rel=tol) 49 | check.almost_equal(sec.section_props.ixx_c, 6.286470e06, rel=tol) 50 | check.almost_equal(sec.section_props.iyy_c, 1.718455e06, rel=tol) 51 | check.almost_equal(sec.section_props.ixy_c, -1.885622e06, rel=tol) 52 | check.almost_equal(sec.section_props.zxx_plus, 6.348769e04, rel=tol) 53 | check.almost_equal(sec.section_props.zxx_minus, 1.233094e05, rel=tol) 54 | check.almost_equal(sec.section_props.zyy_plus, 2.498584e04, rel=tol) 55 | check.almost_equal(sec.section_props.zyy_minus, 8.097207e04, rel=tol) 56 | check.almost_equal(sec.section_props.rx_c, 4.783761e01, rel=tol) 57 | check.almost_equal(sec.section_props.ry_c, 2.501124e01, rel=tol) 58 | check.almost_equal(sec.section_props.i11_c, 6.964263e06, rel=tol) 59 | check.almost_equal(sec.section_props.i22_c, 1.040662e06, rel=tol) 60 | check.almost_equal(sec.section_props.phi, -1.602289e02, rel=tol) 61 | check.almost_equal(sec.section_props.z11_plus, 9.775662e04, rel=tol) 62 | check.almost_equal(sec.section_props.z11_minus, 6.939239e04, rel=tol) 63 | check.almost_equal(sec.section_props.z22_plus, 2.796211e04, rel=tol) 64 | check.almost_equal(sec.section_props.z22_minus, 2.076613e04, rel=tol) 65 | check.almost_equal(sec.section_props.r11_c, 5.035048e01, rel=tol) 66 | check.almost_equal(sec.section_props.r22_c, 1.946350e01, rel=tol) 67 | 68 | 69 | def test_angle_section_warping(ang_section): 70 | """Test angle section warping properties. 71 | 72 | Warping properties calculated from sectionproperties v3.0.2 with a refined mesh 73 | [mesh_sizes=0.5]. 74 | """ 75 | sec = ang_section 76 | sec.calculate_warping_properties() 77 | 78 | x_se, y_se = sec.get_sc() 79 | x11_se, y22_se = sec.get_sc_p() 80 | x_st, y_st = sec.get_sc_t() 81 | 82 | check.almost_equal(sec.section_props.j, 1.354614e05, rel=1.5 * warp_tol) 83 | check.almost_equal(sec.section_props.gamma, 1.622210e08, rel=warp_tol) 84 | check.almost_equal(x_se, 6.124892e00, rel=warp_tol) 85 | check.almost_equal(y_se, 8.126346e00, rel=warp_tol) 86 | check.almost_equal(x11_se, 2.870420e01, rel=warp_tol) 87 | check.almost_equal(y22_se, 3.522160e01, rel=warp_tol) 88 | check.almost_equal(x_st, 6.124892e00, rel=warp_tol) 89 | check.almost_equal(y_st, 8.126346e00, rel=warp_tol) 90 | check.almost_equal(sec.section_props.a_sx, 8.586193e02, rel=warp_tol) 91 | check.almost_equal(sec.section_props.a_sy, 1.539971e03, rel=warp_tol) 92 | check.almost_equal(sec.section_props.a_s11, 8.855853e02, rel=warp_tol) 93 | check.almost_equal(sec.section_props.a_s22, 1.460224e03, rel=warp_tol) 94 | check.almost_equal(sec.section_props.beta_x_plus, -1.061144e02, rel=warp_tol) 95 | check.almost_equal(sec.section_props.beta_x_minus, 1.061144e02, rel=warp_tol) 96 | check.almost_equal(sec.section_props.beta_y_plus, -5.774378e01, rel=warp_tol) 97 | check.almost_equal(sec.section_props.beta_y_minus, 5.774378e01, rel=warp_tol) 98 | check.almost_equal(sec.section_props.beta_11_plus, 8.547674e01, rel=warp_tol) 99 | check.almost_equal(sec.section_props.beta_11_minus, -8.547674e01, rel=warp_tol) 100 | check.almost_equal(sec.section_props.beta_22_plus, 1.419115e02, rel=warp_tol) 101 | check.almost_equal(sec.section_props.beta_22_minus, -1.419115e02, rel=warp_tol) 102 | 103 | 104 | def test_angle_section_plastic(ang_section): 105 | """Test angle section plastic properties. 106 | 107 | Warping properties calculated from sectionproperties v3.0.2 with a refined mesh 108 | [mesh_sizes=0.5]. 109 | """ 110 | sec = ang_section 111 | sec.calculate_plastic_properties() 112 | 113 | x_pc, y_pc = sec.get_pc() 114 | x11_pc, y22_pc = sec.get_pc_p() 115 | 116 | check.almost_equal(x_pc, 9.159481e00, rel=tol) 117 | check.almost_equal(y_pc, 3.507843e01, rel=tol) 118 | check.almost_equal(x11_pc, 2.311371e01, rel=tol) 119 | check.almost_equal(y22_pc, 4.123001e01, rel=tol) 120 | check.almost_equal(sec.section_props.sxx, 1.135392e05, rel=tol) 121 | check.almost_equal(sec.section_props.syy, 4.572265e04, rel=tol) 122 | check.almost_equal(sec.section_props.s11, 1.210275e05, rel=tol) 123 | check.almost_equal(sec.section_props.s22, 4.376054e04, rel=tol) 124 | check.almost_equal(sec.section_props.sf_xx_plus, 1.788366e00, rel=tol) 125 | check.almost_equal(sec.section_props.sf_xx_minus, 9.207672e-01, rel=tol) 126 | check.almost_equal(sec.section_props.sf_yy_plus, 1.829943e00, rel=tol) 127 | check.almost_equal(sec.section_props.sf_yy_minus, 5.646718e-01, rel=tol) 128 | check.almost_equal(sec.section_props.sf_11_plus, 1.238049e00, rel=tol) 129 | check.almost_equal(sec.section_props.sf_11_minus, 1.744103e00, rel=tol) 130 | check.almost_equal(sec.section_props.sf_22_plus, 1.564994e00, rel=tol) 131 | check.almost_equal(sec.section_props.sf_22_minus, 2.107303e00, rel=tol) 132 | -------------------------------------------------------------------------------- /tests/validation/test_custom_validation.py: -------------------------------------------------------------------------------- 1 | """Validation of cross-section properties for a custom section.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | import pytest_check as check 7 | from shapely import Polygon 8 | 9 | from sectionproperties.analysis import Section 10 | from sectionproperties.pre import Geometry 11 | 12 | # constants 13 | tol = 1e-6 14 | plastic_tol = 1e-5 15 | warp_tol = 1e-3 16 | 17 | 18 | @pytest.fixture 19 | def custom_section() -> Section: 20 | """Creates a custom section with a 5 units square mesh. 21 | 22 | Returns: 23 | Section object 24 | """ 25 | points = [ 26 | [-10, 0], 27 | [110, 0], 28 | [100, 10], 29 | [55, 10], 30 | [55, 90], 31 | [100, 90], 32 | [110, 100], 33 | [110, 110], 34 | [-10, 110], 35 | [-10, 100], 36 | [0, 90], 37 | [45, 90], 38 | [45, 10], 39 | [-10, 10], 40 | ] 41 | geom = Geometry(Polygon(points)) 42 | geom.create_mesh(mesh_sizes=5) 43 | sec = Section(geometry=geom) 44 | sec.calculate_geometric_properties() 45 | return sec 46 | 47 | 48 | def test_custom_section_geometric(custom_section): 49 | """Test custom section geometric properties. 50 | 51 | Geometric properties calculated from sectionproperties v3.0.2 with a refined mesh 52 | [mesh_sizes=0.5]. 53 | """ 54 | sec = custom_section 55 | 56 | check.almost_equal(sec.section_props.area, 4250, rel=tol) 57 | check.almost_equal(sec.section_props.perimeter, 6.624264e02, rel=tol) 58 | check.almost_equal(sec.section_props.mass, 4250, rel=tol) 59 | check.almost_equal(sec.section_props.ea, 4250, rel=tol) 60 | check.almost_equal(sec.section_props.qx, 2.763333e05, rel=tol) 61 | check.almost_equal(sec.section_props.qy, 2.096667e05, rel=tol) 62 | check.almost_equal(sec.section_props.ixx_g, 2.567250e07, rel=tol) 63 | check.almost_equal(sec.section_props.iyy_g, 1.418583e07, rel=tol) 64 | check.almost_equal(sec.section_props.ixy_g, 1.379792e07, rel=tol) 65 | check.almost_equal(sec.section_props.cx, 4.933333e01, rel=tol) 66 | check.almost_equal(sec.section_props.cy, 6.501961e01, rel=tol) 67 | check.almost_equal(sec.section_props.ixx_c, 7.705415e06, rel=tol) 68 | check.almost_equal(sec.section_props.iyy_c, 3.842278e06, rel=tol) 69 | check.almost_equal(sec.section_props.ixy_c, 1.654722e05, rel=tol) 70 | check.almost_equal(sec.section_props.zxx_plus, 1.713061e05, rel=tol) 71 | check.almost_equal(sec.section_props.zxx_minus, 1.185091e05, rel=tol) 72 | check.almost_equal(sec.section_props.zyy_plus, 6.333425e04, rel=tol) 73 | check.almost_equal(sec.section_props.zyy_minus, 6.475749e04, rel=tol) 74 | check.almost_equal(sec.section_props.rx_c, 4.257979e01, rel=tol) 75 | check.almost_equal(sec.section_props.ry_c, 3.006768e01, rel=tol) 76 | check.almost_equal(sec.section_props.i11_c, 7.712490e06, rel=tol) 77 | check.almost_equal(sec.section_props.i22_c, 3.835203e06, rel=tol) 78 | check.almost_equal(sec.section_props.phi, -2.448209e00, rel=tol) 79 | check.almost_equal(sec.section_props.z11_plus, 1.622630e05, rel=tol) 80 | check.almost_equal(sec.section_props.z11_minus, 1.142680e05, rel=tol) 81 | check.almost_equal(sec.section_props.z22_plus, 6.050295e04, rel=tol) 82 | check.almost_equal(sec.section_props.z22_minus, 6.266613e04, rel=tol) 83 | check.almost_equal(sec.section_props.r11_c, 4.259934e01, rel=tol) 84 | check.almost_equal(sec.section_props.r22_c, 3.003998e01, rel=tol) 85 | 86 | 87 | def test_custom_section_warping(custom_section): 88 | """Test custom section warping properties. 89 | 90 | Warping properties calculated from sectionproperties v3.0.2 with a refined mesh 91 | [mesh_sizes=0.5]. 92 | """ 93 | sec = custom_section 94 | sec.calculate_warping_properties() 95 | 96 | x_se, y_se = sec.get_sc() 97 | x11_se, y22_se = sec.get_sc_p() 98 | x_st, y_st = sec.get_sc_t() 99 | 100 | check.almost_equal(sec.section_props.j, 3.473083e05, rel=2 * warp_tol) 101 | check.almost_equal(sec.section_props.gamma, 7.535654e09, rel=warp_tol) 102 | check.almost_equal(x_se, 5.137486e01, rel=warp_tol) 103 | check.almost_equal(y_se, 6.795580e01, rel=warp_tol) 104 | check.almost_equal(x11_se, 1.914242e00, rel=2 * warp_tol) 105 | check.almost_equal(y22_se, 3.020718e00, rel=2 * warp_tol) 106 | check.almost_equal(x_st, 5.137486e01, rel=warp_tol) 107 | check.almost_equal(y_st, 6.795580e01, rel=warp_tol) 108 | check.almost_equal(sec.section_props.a_sx, 2.952086e03, rel=warp_tol) 109 | check.almost_equal(sec.section_props.a_sy, 9.545730e02, rel=2 * warp_tol) 110 | check.almost_equal(sec.section_props.a_s11, 2.943977e03, rel=warp_tol) 111 | check.almost_equal(sec.section_props.a_s22, 9.554239e02, rel=2 * warp_tol) 112 | check.almost_equal(sec.section_props.beta_x_plus, 2.530083e01, rel=warp_tol) 113 | check.almost_equal(sec.section_props.beta_x_minus, -2.530083e01, rel=warp_tol) 114 | check.almost_equal(sec.section_props.beta_y_plus, 5.645168e00, rel=warp_tol) 115 | check.almost_equal(sec.section_props.beta_y_minus, -5.645168e00, rel=warp_tol) 116 | check.almost_equal(sec.section_props.beta_11_plus, 2.546759e01, rel=warp_tol) 117 | check.almost_equal(sec.section_props.beta_11_minus, -2.546759e01, rel=warp_tol) 118 | check.almost_equal(sec.section_props.beta_22_plus, 3.724649e00, rel=2 * warp_tol) 119 | check.almost_equal(sec.section_props.beta_22_minus, -3.724649e00, rel=2 * warp_tol) 120 | 121 | 122 | def test_custom_section_plastic(custom_section): 123 | """Test custom section plastic properties. 124 | 125 | Plastic properties calculated from sectionproperties v3.0.2 with a refined mesh 126 | [mesh_sizes=0.5]. 127 | """ 128 | sec = custom_section 129 | sec.calculate_plastic_properties() 130 | 131 | x_pc, y_pc = sec.get_pc() 132 | x11_pc, y22_pc = sec.get_pc_p() 133 | 134 | check.almost_equal(x_pc, 4.977273e01, rel=plastic_tol) 135 | check.almost_equal(y_pc, 9.172040e01, rel=plastic_tol) 136 | check.almost_equal(x11_pc, 5.133714e01, rel=plastic_tol) 137 | check.almost_equal(y22_pc, 9.158984e01, rel=plastic_tol) 138 | check.almost_equal(sec.section_props.sxx, 1.531971e05, rel=plastic_tol) 139 | check.almost_equal(sec.section_props.syy, 1.014943e05, rel=plastic_tol) 140 | check.almost_equal(sec.section_props.s11, 1.533463e05, rel=plastic_tol) 141 | check.almost_equal(sec.section_props.s22, 1.015010e05, rel=plastic_tol) 142 | check.almost_equal(sec.section_props.sf_xx_plus, 8.942884e-01, rel=plastic_tol) 143 | check.almost_equal(sec.section_props.sf_xx_minus, 1.292703e00, rel=plastic_tol) 144 | check.almost_equal(sec.section_props.sf_yy_plus, 1.602519e00, rel=plastic_tol) 145 | check.almost_equal(sec.section_props.sf_yy_minus, 1.567298e00, rel=plastic_tol) 146 | check.almost_equal(sec.section_props.sf_11_plus, 9.450478e-01, rel=plastic_tol) 147 | check.almost_equal(sec.section_props.sf_11_minus, 1.341988e00, rel=plastic_tol) 148 | check.almost_equal(sec.section_props.sf_22_plus, 1.677621e00, rel=plastic_tol) 149 | check.almost_equal(sec.section_props.sf_22_minus, 1.619711e00, rel=plastic_tol) 150 | -------------------------------------------------------------------------------- /typings/cad_to_shapely/__init__.pyi: -------------------------------------------------------------------------------- 1 | from .dxf import DxfImporter 2 | from .utils import filter_polygons, find_holes 3 | -------------------------------------------------------------------------------- /typings/cad_to_shapely/dxf.pyi: -------------------------------------------------------------------------------- 1 | from shapely import Polygon 2 | 3 | class DxfImporter: 4 | polygons: list[Polygon] 5 | 6 | def __init__( 7 | self, 8 | filename: str, 9 | ) -> None: ... 10 | def process( 11 | self, 12 | spline_delta: float = ..., 13 | degrees_per_segment: float = ..., 14 | ) -> str: ... 15 | def cleanup( 16 | self, 17 | simplify: bool = ..., 18 | zip_length: float = ..., 19 | retry_with_zip: bool = ..., 20 | ) -> str: ... 21 | -------------------------------------------------------------------------------- /typings/cad_to_shapely/utils.pyi: -------------------------------------------------------------------------------- 1 | from shapely.geometry import Polygon 2 | 3 | def find_holes(polygons: list[Polygon]) -> Polygon: ... 4 | def filter_polygons( 5 | polygons: list[Polygon], 6 | filter_flag: int = ..., 7 | ) -> list[Polygon]: ... 8 | -------------------------------------------------------------------------------- /typings/cytriangle/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | def triangulate( 4 | input_dict: dict[str, Any], 5 | flags: str, 6 | ) -> dict[str, Any]: ... 7 | -------------------------------------------------------------------------------- /typings/mpl_toolkits/axes_grid1/axes_divider.pyi: -------------------------------------------------------------------------------- 1 | import matplotlib.axes 2 | 3 | def make_axes_locatable(axes: matplotlib.axes.Axes) -> None: ... 4 | -------------------------------------------------------------------------------- /typings/numba/__init__.pyi: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from typing import Any 3 | 4 | def njit( 5 | cache: bool, 6 | nogil: bool, 7 | ) -> Callable[[Any], Any]: ... 8 | -------------------------------------------------------------------------------- /typings/pypardiso/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import numpy as np 4 | import numpy.typing as npt 5 | from scipy.sparse import coo_matrix, csc_matrix 6 | 7 | def spsolve( 8 | A: npt.NDArray[np.float64] | csc_matrix | coo_matrix, 9 | b: npt.NDArray[np.float64] | csc_matrix | coo_matrix, 10 | ) -> npt.NDArray[np.float64]: ... 11 | -------------------------------------------------------------------------------- /typings/rhino_shapely_interop/importers.pyi: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterator 2 | 3 | import numpy as np 4 | import numpy.typing as npt 5 | from shapely.geometry import Polygon 6 | 7 | class RhImporter: 8 | @classmethod 9 | def from_file( 10 | cls, 11 | file_name: str, 12 | ) -> RhImporter: ... 13 | @classmethod 14 | def from_serialzed_brep( 15 | cls, 16 | s_brep: str, 17 | ) -> RhImporter: ... 18 | def get_planer_brep( 19 | self, 20 | refine_num: int = ..., 21 | tol: float = ..., 22 | vec1: npt.NDArray[np.float64] = ..., 23 | vec2: npt.NDArray[np.float64] = ..., 24 | plane_distance: float = ..., 25 | project: bool = ..., 26 | parallel: bool = ..., 27 | ) -> Iterator[Polygon]: ... 28 | -------------------------------------------------------------------------------- /typings/scipy/optimize/__init__.pyi: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from typing import Any 3 | 4 | def brentq( 5 | f: Callable[[float, Any, Any, Any], float], 6 | a: float, 7 | b: float, 8 | args: tuple[Any, ...] = ..., 9 | xtol: float = ..., 10 | rtol: float = ..., 11 | maxiter: int = ..., 12 | full_output: bool = ..., 13 | disp: bool = ..., 14 | ) -> tuple[float, RootResults]: ... 15 | 16 | class RootResults: 17 | root: float 18 | iterations: int 19 | function_calls: int 20 | converged: bool 21 | flag: str 22 | method: str 23 | -------------------------------------------------------------------------------- /typings/scipy/sparse/__init__.pyi: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class csc_matrix: 4 | def __init__( 5 | self, 6 | data: coo_matrix, 7 | dtype: np.dtype | type, 8 | ) -> None: ... 9 | 10 | class coo_matrix: 11 | def __init__( 12 | self, 13 | data: tuple, 14 | shape: tuple[int, int], 15 | dtype: np.dtype | type, 16 | ) -> None: ... 17 | -------------------------------------------------------------------------------- /typings/scipy/sparse/linalg.pyi: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from typing import Any 3 | 4 | import numpy as np 5 | import numpy.typing as npt 6 | from scipy.sparse import coo_matrix, csc_matrix 7 | 8 | class LinearOperator: 9 | def __init__( 10 | self, 11 | shape: tuple, 12 | matvec: Callable, 13 | ) -> None: ... 14 | 15 | def spsolve( 16 | A: npt.NDArray[np.float64] | csc_matrix | coo_matrix, 17 | b: npt.NDArray[np.float64] | csc_matrix | coo_matrix, 18 | permc_spec: str = ..., 19 | use_umfpack: bool = ..., 20 | ) -> npt.NDArray[np.float64]: ... 21 | def cgs( 22 | A: npt.NDArray[np.float64] | csc_matrix | coo_matrix, 23 | b: npt.NDArray[np.float64], 24 | x0: npt.NDArray[np.float64] = ..., 25 | rtol: float = ..., 26 | atol: float = ..., 27 | maxiter: int = ..., 28 | M: LinearOperator = ..., 29 | callback: Callable[[Any], Any] = ..., 30 | ) -> tuple[npt.NDArray[np.float64], int]: ... 31 | def spilu(A: csc_matrix | coo_matrix) -> SuperLU: ... 32 | 33 | class SuperLU: 34 | def solve( 35 | self, 36 | rhs: npt.NDArray[np.float64] | tuple[Any], 37 | ) -> npt.NDArray[np.float64] | tuple[Any]: ... 38 | --------------------------------------------------------------------------------