├── .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 |
5 |
6 |
7 | [][pypi_]
8 | [][status]
9 | [][python version]
10 | [][license]
11 | [][read the docs]
12 | [][uv]
13 | [][ruff]
14 | [][pre-commit]
15 | [][tests]
16 | [][codecov]
17 | [][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 |
--------------------------------------------------------------------------------