From 9a8bb9bea4240de6ef7ccaabceaa7192fe3ac7e5 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 3 Sep 2024 11:27:27 +0200 Subject: [PATCH 01/96] Add step for testing on the released server (#573) Add a step to the tests on Python 3.12 for testing with the 2024R2 server version. Expected test failures (new features) are marked with the new `xfail_before` fixture. The only current expected failure is the modeling ply geometry export test (added in 25.1). Also fixes an issue with the `clone` method when `unlink=True` is specified: Any fields unknown to the current client were retained. This caused an error on storing, since those may be unknown linked objects. In the current case, the newly added links to the `Fabric` caused this. Calling `DiscardUnknownFields()` on the protobuf message fixes this. --- .github/workflows/ci_cd.yml | 10 ++++++++++ src/ansys/acp/core/_tree_objects/base.py | 3 +++ tests/conftest.py | 12 ++++++++++++ tests/unittests/test_model.py | 3 ++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 274d8946f4..61d8ac80c3 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -172,6 +172,16 @@ jobs: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} IMAGE_NAME: "ghcr.io/ansys/acp${{ github.event.inputs.docker_image_suffix || ':latest' }}" + - name: "Unit testing (2024R2 server)" + if: matrix.python-version == env.MAIN_PYTHON_VERSION + working-directory: tests/unittests + run: | + docker pull $IMAGE_NAME + poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html --cov-append + env: + LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} + IMAGE_NAME: "ghcr.io/ansys/acp:2024r2" + - name: "Upload coverage report (HTML)" uses: actions/upload-artifact@v4 if: matrix.python-version == env.MAIN_PYTHON_VERSION diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index 42c7afac8e..7f89e02d73 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -285,6 +285,9 @@ def clone(self: Self, *, unlink: bool = False) -> Self: new_object_info.properties.CopyFrom(self._pb_object.properties) if unlink: unlink_objects(new_object_info.properties) + # Since there may be links in the unknown fields, we need to + # discard them to avoid errors when storing the object. + new_object_info.properties.DiscardUnknownFields() # type: ignore new_object_info.info.name = self._pb_object.info.name return type(self)._from_object_info(object_info=new_object_info) diff --git a/tests/conftest.py b/tests/conftest.py index 47183c3827..e09540c005 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,6 +31,7 @@ import docker from hypothesis import settings +from packaging.version import parse as parse_version import pytest from ansys.acp.core import ( @@ -260,3 +261,14 @@ def inner(model, relative_file_path="square_and_solid.stp"): ) return inner + + +@pytest.fixture +def xfail_before(acp_instance): + """Mark a test as expected to fail before a certain server version.""" + + def inner(version: str): + if parse_version(acp_instance.server_version) < parse_version(version): + pytest.xfail(f"Expected to fail until server version {version!r}") + + return inner diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 70b3f7bbf2..d32bf6b368 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -251,11 +251,12 @@ def test_regression_454(minimal_complete_model): assert not hasattr(minimal_complete_model, "store") -def test_modeling_ply_export(acp_instance, minimal_complete_model): +def test_modeling_ply_export(acp_instance, minimal_complete_model, xfail_before): """ Test that the 'export_modeling_ply_geometries' method produces a file. The contents of the file are not checked. """ + xfail_before("25.1") out_filename = "modeling_ply_export.step" with tempfile.TemporaryDirectory() as tmp_dir: From c230e7d43c85e53c786b23d737286741714e4890 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 01:33:42 +0000 Subject: [PATCH 02/96] Bump the dependencies group across 1 directory with 5 updates Bumps the dependencies group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [ansys-mechanical-core](https://github.com/ansys/pymechanical) | `0.11.5` | `0.11.7` | | [types-protobuf](https://github.com/python/typeshed) | `5.27.0.20240626` | `5.27.0.20240907` | | [sphinx-autodoc-typehints](https://github.com/tox-dev/sphinx-autodoc-typehints) | `2.2.3` | `2.3.0` | | [ansys-sphinx-theme](https://github.com/ansys/ansys-sphinx-theme) | `1.0.7` | `1.0.8` | | [hypothesis](https://github.com/HypothesisWorks/hypothesis) | `6.111.2` | `6.112.0` | Updates `ansys-mechanical-core` from 0.11.5 to 0.11.7 - [Release notes](https://github.com/ansys/pymechanical/releases) - [Changelog](https://github.com/ansys/pymechanical/blob/main/CHANGELOG.md) - [Commits](https://github.com/ansys/pymechanical/compare/v0.11.5...v0.11.7) Updates `types-protobuf` from 5.27.0.20240626 to 5.27.0.20240907 - [Commits](https://github.com/python/typeshed/commits) Updates `sphinx-autodoc-typehints` from 2.2.3 to 2.3.0 - [Release notes](https://github.com/tox-dev/sphinx-autodoc-typehints/releases) - [Changelog](https://github.com/tox-dev/sphinx-autodoc-typehints/blob/main/CHANGELOG.md) - [Commits](https://github.com/tox-dev/sphinx-autodoc-typehints/compare/2.2.3...2.3.0) Updates `ansys-sphinx-theme` from 1.0.7 to 1.0.8 - [Release notes](https://github.com/ansys/ansys-sphinx-theme/releases) - [Commits](https://github.com/ansys/ansys-sphinx-theme/compare/v1.0.7...v1.0.8) Updates `hypothesis` from 6.111.2 to 6.112.0 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.111.2...hypothesis-python-6.112.0) --- updated-dependencies: - dependency-name: ansys-mechanical-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: types-protobuf dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: sphinx-autodoc-typehints dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: ansys-sphinx-theme dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- poetry.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 50a52fa99a..feae32914b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -377,13 +377,13 @@ tests = ["ansys-mapdl-core (==0.68.1)", "numpy (==1.26.4)", "pyansys-tools-repor [[package]] name = "ansys-mechanical-core" -version = "0.11.5" +version = "0.11.7" description = "A python wrapper for Ansys Mechanical" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "ansys_mechanical_core-0.11.5-py3-none-any.whl", hash = "sha256:28a3ef8777a7a650ab6b4a2f853aa2837a840f7c76ceb34b55edd3ef5a22091c"}, - {file = "ansys_mechanical_core-0.11.5.tar.gz", hash = "sha256:70f3dc062b64af8a3e35b5ea1e854862456dd9f4a2a9642e51017491f619f60e"}, + {file = "ansys_mechanical_core-0.11.7-py3-none-any.whl", hash = "sha256:e13a98a87648ea80ad3c9d3901504d05c3500b359b0f88dfb06bc066e97783f1"}, + {file = "ansys_mechanical_core-0.11.7.tar.gz", hash = "sha256:6c54a5b4504ff3ffa3823e1c8e53c91b6d2f182c6fe4f6fd553f68a3e882f940"}, ] [package.dependencies] @@ -400,7 +400,7 @@ protobuf = ">=3.12.2,<6" tqdm = ">=4.45.0" [package.extras] -doc = ["ansys-sphinx-theme[autoapi] (==1.0.3)", "grpcio (==1.65.4)", "imageio (==2.34.2)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.1.post1)", "numpy (==2.0.1)", "numpydoc (==1.8.0)", "pandas (==2.2.2)", "panel (==1.4.5)", "plotly (==5.23.0)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.0.2)", "sphinx-autobuild (==2024.4.16)", "sphinx-autodoc-typehints (==2.2.3)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.17.1)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] +doc = ["ansys-sphinx-theme[autoapi] (==1.0.7)", "grpcio (==1.66.0)", "imageio (==2.35.1)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.0)", "numpydoc (==1.8.0)", "pandas (==2.2.2)", "panel (==1.4.5)", "plotly (==5.23.0)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.0.2)", "sphinx-autobuild (==2024.4.16)", "sphinx-autodoc-typehints (==2.2.3)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.17.1)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] tests = ["pytest (==8.3.2)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.0)"] viz = ["ansys-tools-visualization-interface (>=0.2.6)", "usd-core (==24.8)"] @@ -455,13 +455,13 @@ clr-loader = ">=0.2.6,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.0.7" +version = "1.0.8" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.9" files = [ - {file = "ansys_sphinx_theme-1.0.7-py3-none-any.whl", hash = "sha256:8d26b975bf4e332cbc32753983824c0233d4d62920991a6914c4e24b126390d4"}, - {file = "ansys_sphinx_theme-1.0.7.tar.gz", hash = "sha256:c4b9b012f5455c3e94b123f553ead59bb97bf235ab565032e895c0088d08fb51"}, + {file = "ansys_sphinx_theme-1.0.8-py3-none-any.whl", hash = "sha256:c212239c44691f8d5a5b09b3b5dd4322edbd5935793420b6eadbde0d18af2286"}, + {file = "ansys_sphinx_theme-1.0.8.tar.gz", hash = "sha256:e7a6adf9072541b6e7492ea8ae399fb3228c6022612d9fc9df97c7c73ef78cc3"}, ] [package.dependencies] @@ -1769,13 +1769,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.111.2" +version = "6.112.0" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.111.2-py3-none-any.whl", hash = "sha256:055e8228958e22178d6077e455fd86a72044d02dac130dbf9c8b31e161b9809c"}, - {file = "hypothesis-6.111.2.tar.gz", hash = "sha256:0496ad28c7240ee9ba89fcc7fb1dc74e89f3e40fbcbbb5f73c0091558dec8e6e"}, + {file = "hypothesis-6.112.0-py3-none-any.whl", hash = "sha256:1e6adbd9534c0d691690b5006904327ea37c851d4e15262a22094aa77879e84d"}, + {file = "hypothesis-6.112.0.tar.gz", hash = "sha256:06ea8857e1e711a1a6f24154a3c8c4eab04b041993206aaa267f98b859fd6ef5"}, ] [package.dependencies] @@ -4193,13 +4193,13 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools [[package]] name = "sphinx-autodoc-typehints" -version = "2.2.3" +version = "2.3.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false python-versions = ">=3.9" files = [ - {file = "sphinx_autodoc_typehints-2.2.3-py3-none-any.whl", hash = "sha256:b7058e8c5831e5598afca1a78fda0695d3291388d954464a6e480c36198680c0"}, - {file = "sphinx_autodoc_typehints-2.2.3.tar.gz", hash = "sha256:fde3d888949bd0a91207cf1e54afda58121dbb4bf1f183d0cc78a0826654c974"}, + {file = "sphinx_autodoc_typehints-2.3.0-py3-none-any.whl", hash = "sha256:3098e2c6d0ba99eacd013eb06861acc9b51c6e595be86ab05c08ee5506ac0c67"}, + {file = "sphinx_autodoc_typehints-2.3.0.tar.gz", hash = "sha256:535c78ed2d6a1bad393ba9f3dfa2602cf424e2631ee207263e07874c38fde084"}, ] [package.dependencies] @@ -4599,13 +4599,13 @@ trame-client = "*" [[package]] name = "types-protobuf" -version = "5.27.0.20240626" +version = "5.27.0.20240907" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types-protobuf-5.27.0.20240626.tar.gz", hash = "sha256:683ba14043bade6785e3f937a7498f243b37881a91ac8d81b9202ecf8b191e9c"}, - {file = "types_protobuf-5.27.0.20240626-py3-none-any.whl", hash = "sha256:688e8f7e8d9295db26bc560df01fb731b27a25b77cbe4c1ce945647f7024f5c1"}, + {file = "types-protobuf-5.27.0.20240907.tar.gz", hash = "sha256:bb6f90f66b18d4d1c75667b6586334b0573a6fcee5eb0142a7348a765a7cbadc"}, + {file = "types_protobuf-5.27.0.20240907-py3-none-any.whl", hash = "sha256:5443270534cc8072909ef7ad9e1421ccff924ca658749a6396c0c43d64c32676"}, ] [[package]] From 90189482a8ad4cfc74057282bc84fb769716a4e6 Mon Sep 17 00:00:00 2001 From: Kathy Pippert <84872299+PipKat@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:30:34 -0400 Subject: [PATCH 03/96] Doc/minor edits (#577) * Standardize "Contribute" heading and make minor edits for overall doc consistency * minor edits --- CONTRIBUTING.md | 13 ++++++------- doc/source/contributing.rst | 18 +++++++++--------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b77db6d65e..007f97120e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,17 +2,16 @@ -We absolutely welcome any code contributions and we hope that this -guide will facilitate an understanding of the PyACP code +We absolutely welcome all code contributions and hope that this +guide facilitates an understanding of the PyACP code repository. It is important to note that while the PyACP software -package is maintained by ANSYS and any submissions will be reviewed +package is maintained by Ansys and any submissions are reviewed thoroughly before merging, we still seek to foster a community that can support user questions and develop new features to make this software -a useful tool for all users. As such, we welcome and encourage any +a useful tool for all users. As such, we welcome and encourage any questions or submissions to this repository. -Please reference the [PyAnsys Developer's -Guide](https://dev.docs.pyansys.com) for the full documentation -regarding contributing to the PyACP project. +For comprehensive documentation on contributing to the PyACP project, +refer to the [PyAnsys developer's guide](https://dev.docs.pyansys.com). diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index 9cd1fde812..5a8a2ce24d 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -1,21 +1,21 @@ -Contributing -============ +Contribute +========== .. include:: ../../CONTRIBUTING.md :start-after: :end-before: -For example, you could contribute by: +For example, you can make contributions as follows: -- Reporting a bug or suggesting a feature -- Giving feedback or making suggestions for improving the documentation -- Submitting a pull request to fix a bug or add a feature -- Reporting your use case, and how PyACP helped you or what is still missing +- Report a bug or suggest a feature. +- Give feedback or make suggestions for improving the documentation. +- Submit a pull request to fix a bug or add a feature. +- Report your use case, explain how PyACP has helped you, or indicate what features are still missing. -For feedback, suggestions, or bug reports, please open an `issue +For feedback, suggestions, or bug reports, open an `issue `_ on the PyACP GitHub repository. -For code changes or documentation improvements, please open a `pull request +For code changes or documentation improvements, open a `pull request `_. From 7edaf34e49bd1535c235c94954b57c6094d17365 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 17 Sep 2024 11:28:24 +0200 Subject: [PATCH 04/96] Adapt to changes in the API library (#580) Adapt to the change moving the file format definition to `enum_types.proto`: Adds an optional argument `explicit_value_list` to the enum wrapper, which specifies which of the wrapped values are exposed. This is used to wrap the `enum_types_pb2.FileFormat` in different ways, exposing only the file formats acceptable for a given use. --- poetry.lock | 1779 +++++++++-------- pyproject.toml | 2 +- .../_grpc_helpers/enum_wrapper.py | 6 +- src/ansys/acp/core/_tree_objects/enums.py | 8 +- src/ansys/acp/core/_tree_objects/model.py | 8 +- 5 files changed, 949 insertions(+), 854 deletions(-) diff --git a/poetry.lock b/poetry.lock index feae32914b..09f6bfd879 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,98 +20,113 @@ tests = ["hypothesis", "pytest"] [[package]] name = "aiohappyeyeballs" -version = "2.3.6" +version = "2.4.0" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.3.6-py3-none-any.whl", hash = "sha256:15dca2611fa78442f1cb54cf07ffb998573f2b4fbeab45ca8554c045665c896b"}, - {file = "aiohappyeyeballs-2.3.6.tar.gz", hash = "sha256:88211068d2a40e0436033956d7de3926ff36d54776f8b1022d6b21320cadae79"}, + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, ] [[package]] name = "aiohttp" -version = "3.10.3" +version = "3.10.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc36cbdedf6f259371dbbbcaae5bb0e95b879bc501668ab6306af867577eb5db"}, - {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85466b5a695c2a7db13eb2c200af552d13e6a9313d7fa92e4ffe04a2c0ea74c1"}, - {file = "aiohttp-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71bb1d97bfe7e6726267cea169fdf5df7658831bb68ec02c9c6b9f3511e108bb"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baec1eb274f78b2de54471fc4c69ecbea4275965eab4b556ef7a7698dee18bf2"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13031e7ec1188274bad243255c328cc3019e36a5a907978501256000d57a7201"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bbc55a964b8eecb341e492ae91c3bd0848324d313e1e71a27e3d96e6ee7e8e8"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8cc0564b286b625e673a2615ede60a1704d0cbbf1b24604e28c31ed37dc62aa"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f817a54059a4cfbc385a7f51696359c642088710e731e8df80d0607193ed2b73"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8542c9e5bcb2bd3115acdf5adc41cda394e7360916197805e7e32b93d821ef93"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:671efce3a4a0281060edf9a07a2f7e6230dca3a1cbc61d110eee7753d28405f7"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0974f3b5b0132edcec92c3306f858ad4356a63d26b18021d859c9927616ebf27"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:44bb159b55926b57812dca1b21c34528e800963ffe130d08b049b2d6b994ada7"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6ae9ae382d1c9617a91647575255ad55a48bfdde34cc2185dd558ce476bf16e9"}, - {file = "aiohttp-3.10.3-cp310-cp310-win32.whl", hash = "sha256:aed12a54d4e1ee647376fa541e1b7621505001f9f939debf51397b9329fd88b9"}, - {file = "aiohttp-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b51aef59370baf7444de1572f7830f59ddbabd04e5292fa4218d02f085f8d299"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e021c4c778644e8cdc09487d65564265e6b149896a17d7c0f52e9a088cc44e1b"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24fade6dae446b183e2410a8628b80df9b7a42205c6bfc2eff783cbeedc224a2"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc8e9f15939dacb0e1f2d15f9c41b786051c10472c7a926f5771e99b49a5957f"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a9ec959b5381271c8ec9310aae1713b2aec29efa32e232e5ef7dcca0df0279"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a5d0ea8a6467b15d53b00c4e8ea8811e47c3cc1bdbc62b1aceb3076403d551f"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9ed607dbbdd0d4d39b597e5bf6b0d40d844dfb0ac6a123ed79042ef08c1f87e"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e66d5b506832e56add66af88c288c1d5ba0c38b535a1a59e436b300b57b23e"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fda91ad797e4914cca0afa8b6cccd5d2b3569ccc88731be202f6adce39503189"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:61ccb867b2f2f53df6598eb2a93329b5eee0b00646ee79ea67d68844747a418e"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d881353264e6156f215b3cb778c9ac3184f5465c2ece5e6fce82e68946868ef"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b031ce229114825f49cec4434fa844ccb5225e266c3e146cb4bdd025a6da52f1"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5337cc742a03f9e3213b097abff8781f79de7190bbfaa987bd2b7ceb5bb0bdec"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab3361159fd3dcd0e48bbe804006d5cfb074b382666e6c064112056eb234f1a9"}, - {file = "aiohttp-3.10.3-cp311-cp311-win32.whl", hash = "sha256:05d66203a530209cbe40f102ebaac0b2214aba2a33c075d0bf825987c36f1f0b"}, - {file = "aiohttp-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:70b4a4984a70a2322b70e088d654528129783ac1ebbf7dd76627b3bd22db2f17"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166de65e2e4e63357cfa8417cf952a519ac42f1654cb2d43ed76899e2319b1ee"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7084876352ba3833d5d214e02b32d794e3fd9cf21fdba99cff5acabeb90d9806"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d98c604c93403288591d7d6d7d6cc8a63459168f8846aeffd5b3a7f3b3e5e09"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d73b073a25a0bb8bf014345374fe2d0f63681ab5da4c22f9d2025ca3e3ea54fc"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8da6b48c20ce78f5721068f383e0e113dde034e868f1b2f5ee7cb1e95f91db57"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a9dcdccf50284b1b0dc72bc57e5bbd3cc9bf019060dfa0668f63241ccc16aa7"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56fb94bae2be58f68d000d046172d8b8e6b1b571eb02ceee5535e9633dcd559c"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf75716377aad2c718cdf66451c5cf02042085d84522aec1f9246d3e4b8641a6"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c51ed03e19c885c8e91f574e4bbe7381793f56f93229731597e4a499ffef2a5"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b84857b66fa6510a163bb083c1199d1ee091a40163cfcbbd0642495fed096204"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c124b9206b1befe0491f48185fd30a0dd51b0f4e0e7e43ac1236066215aff272"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3461d9294941937f07bbbaa6227ba799bc71cc3b22c40222568dc1cca5118f68"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08bd0754d257b2db27d6bab208c74601df6f21bfe4cb2ec7b258ba691aac64b3"}, - {file = "aiohttp-3.10.3-cp312-cp312-win32.whl", hash = "sha256:7f9159ae530297f61a00116771e57516f89a3de6ba33f314402e41560872b50a"}, - {file = "aiohttp-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:e1128c5d3a466279cb23c4aa32a0f6cb0e7d2961e74e9e421f90e74f75ec1edf"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d1100e68e70eb72eadba2b932b185ebf0f28fd2f0dbfe576cfa9d9894ef49752"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a541414578ff47c0a9b0b8b77381ea86b0c8531ab37fc587572cb662ccd80b88"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5548444ef60bf4c7b19ace21f032fa42d822e516a6940d36579f7bfa8513f9c"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba2e838b5e6a8755ac8297275c9460e729dc1522b6454aee1766c6de6d56e5e"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48665433bb59144aaf502c324694bec25867eb6630fcd831f7a893ca473fcde4"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bac352fceed158620ce2d701ad39d4c1c76d114255a7c530e057e2b9f55bdf9f"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0f670502100cdc567188c49415bebba947eb3edaa2028e1a50dd81bd13363f"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b09f38a67679e32d380fe512189ccb0b25e15afc79b23fbd5b5e48e4fc8fd9"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:cd788602e239ace64f257d1c9d39898ca65525583f0fbf0988bcba19418fe93f"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:214277dcb07ab3875f17ee1c777d446dcce75bea85846849cc9d139ab8f5081f"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:32007fdcaab789689c2ecaaf4b71f8e37bf012a15cd02c0a9db8c4d0e7989fa8"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:123e5819bfe1b87204575515cf448ab3bf1489cdeb3b61012bde716cda5853e7"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:812121a201f0c02491a5db335a737b4113151926a79ae9ed1a9f41ea225c0e3f"}, - {file = "aiohttp-3.10.3-cp38-cp38-win32.whl", hash = "sha256:b97dc9a17a59f350c0caa453a3cb35671a2ffa3a29a6ef3568b523b9113d84e5"}, - {file = "aiohttp-3.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:3731a73ddc26969d65f90471c635abd4e1546a25299b687e654ea6d2fc052394"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38d91b98b4320ffe66efa56cb0f614a05af53b675ce1b8607cdb2ac826a8d58e"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9743fa34a10a36ddd448bba8a3adc2a66a1c575c3c2940301bacd6cc896c6bf1"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c126f532caf238031c19d169cfae3c6a59129452c990a6e84d6e7b198a001dc"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926e68438f05703e500b06fe7148ef3013dd6f276de65c68558fa9974eeb59ad"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:434b3ab75833accd0b931d11874e206e816f6e6626fd69f643d6a8269cd9166a"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d35235a44ec38109b811c3600d15d8383297a8fab8e3dec6147477ec8636712a"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59c489661edbd863edb30a8bd69ecb044bd381d1818022bc698ba1b6f80e5dd1"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50544fe498c81cb98912afabfc4e4d9d85e89f86238348e3712f7ca6a2f01dab"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09bc79275737d4dc066e0ae2951866bb36d9c6b460cb7564f111cc0427f14844"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:af4dbec58e37f5afff4f91cdf235e8e4b0bd0127a2a4fd1040e2cad3369d2f06"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b22cae3c9dd55a6b4c48c63081d31c00fc11fa9db1a20c8a50ee38c1a29539d2"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ba562736d3fbfe9241dad46c1a8994478d4a0e50796d80e29d50cabe8fbfcc3f"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f25d6c4e82d7489be84f2b1c8212fafc021b3731abdb61a563c90e37cced3a21"}, - {file = "aiohttp-3.10.3-cp39-cp39-win32.whl", hash = "sha256:b69d832e5f5fa15b1b6b2c8eb6a9fd2c0ec1fd7729cb4322ed27771afc9fc2ac"}, - {file = "aiohttp-3.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:673bb6e3249dc8825df1105f6ef74e2eab779b7ff78e96c15cadb78b04a83752"}, - {file = "aiohttp-3.10.3.tar.gz", hash = "sha256:21650e7032cc2d31fc23d353d7123e771354f2a3d5b05a5647fc30fea214e696"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, ] [package.dependencies] @@ -153,13 +168,13 @@ files = [ [[package]] name = "ansys-api-acp" -version = "0.1.0.dev9" +version = "0.2.0.dev0" description = "Autogenerated Python package for the Ansys Composite PrepPost (ACP) gRPC API." optional = false python-versions = ">=3.7" files = [ - {file = "ansys_api_acp-0.1.0.dev9-py3-none-any.whl", hash = "sha256:003795e8a3f038edecf93b1031da3d6e809338ee5e42be40f0182d0bdf6782b1"}, - {file = "ansys_api_acp-0.1.0.dev9.tar.gz", hash = "sha256:5ade9e3c9d9da091202a5e55286f8afa5b0f30d2a58133414730bc879356d565"}, + {file = "ansys_api_acp-0.2.0.dev0-py3-none-any.whl", hash = "sha256:888428ec47fbb9e5c9adf7f3a0eee3dd4e09e9c0606097b5e5ae3fdf29662534"}, + {file = "ansys_api_acp-0.2.0.dev0.tar.gz", hash = "sha256:a5d662f2ec279a97fd026f61d1fdc50a6367624d23c6efd080730dce8748a9a8"}, ] [package.dependencies] @@ -228,13 +243,13 @@ protobuf = ">=3.19,<5" [[package]] name = "ansys-dpf-composites" -version = "0.6.0" +version = "0.6.1" description = "Post-processing of composite structures based on Ansys DPF" optional = false python-versions = "<3.13,>=3.9" files = [ - {file = "ansys_dpf_composites-0.6.0-py3-none-any.whl", hash = "sha256:f77c2f6b24884e717b3d3c7ae5eb0ddc0cc63a67a6d996599639f2c5aeacea86"}, - {file = "ansys_dpf_composites-0.6.0.tar.gz", hash = "sha256:5e0d79cf878f14716e6497fe8a65dcab8621e40fcd47808aee4ee9337996bb77"}, + {file = "ansys_dpf_composites-0.6.1-py3-none-any.whl", hash = "sha256:8d78dd9e1974c1b241c57a98c9a25ec138cf8ad5e997c803ab464ce38e118b64"}, + {file = "ansys_dpf_composites-0.6.1.tar.gz", hash = "sha256:a3feb7813ea7a2fd92c1a1bd1425b57de8d248e18f98b33d2abe883d1f95bbbe"}, ] [package.dependencies] @@ -318,37 +333,37 @@ tests = ["ansys-dpf-core (==0.10.1)", "autopep8 (==2.3.1)", "matplotlib (==3.9.1 [[package]] name = "ansys-mapdl-reader" -version = "0.53.0" +version = "0.54.1" description = "Pythonic interface to files generated by MAPDL" optional = false -python-versions = ">=3.7,<4" -files = [ - {file = "ansys-mapdl-reader-0.53.0.tar.gz", hash = "sha256:c7cd8ae1f2a3d6b86a0e443fc02f9d5139a06f49968672e9e417b98a558b8f5d"}, - {file = "ansys_mapdl_reader-0.53.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a100b0fb0be0d64ad8286cda6a7e70e4fcf80d10424504565a08af22c2484616"}, - {file = "ansys_mapdl_reader-0.53.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29b362179a7ce4febb1a205068a5ce1852faae95e0bf05eef037a46eee3ec73f"}, - {file = "ansys_mapdl_reader-0.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80cd2eded333358bc2b0e4eb4590dc2fc17e81258f340830cfc901f5f2cd256b"}, - {file = "ansys_mapdl_reader-0.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:95487623058ba1f287513dbd282b069df2c48e6dfa2adfbe94aec8e374b80266"}, - {file = "ansys_mapdl_reader-0.53.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2731c2b73b6d3aafe47343b4d5225ff04346dfca7291855c7f5c031804671d3c"}, - {file = "ansys_mapdl_reader-0.53.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:032712a9624974f390d5c2f0f8867e3b78015f7accbd898784cf7e3d992b80ff"}, - {file = "ansys_mapdl_reader-0.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ef0c57448f8beabc06145e2f9b7b59d3fb2f0ae9fb5cddfba67b7969b9249b6"}, - {file = "ansys_mapdl_reader-0.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:40a418a7a7da89c1fbe46a022096d168096f79f80e538113cc5b43dc37d7bcb9"}, - {file = "ansys_mapdl_reader-0.53.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5e3eff1c9e9478f78fe2bb5516b0279587c8a4bbf5b4b5c064aba51d41affced"}, - {file = "ansys_mapdl_reader-0.53.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e328139934287fe6589cc4201a98a51b0f10e4876db2bb394cf063bf35828a9b"}, - {file = "ansys_mapdl_reader-0.53.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4945065db8bb36ba83341dcdd762c20e60f93fe1d6e917046908cae41261b8f"}, - {file = "ansys_mapdl_reader-0.53.0-cp312-cp312-win_amd64.whl", hash = "sha256:6ec0ba7da3463953ef8688b6639ce99e2866edf54792494c81228eccdf617bfa"}, - {file = "ansys_mapdl_reader-0.53.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c5e796b474b7968443f6304d00c93b9351cce8dd3aafbaf7643c60a90a3315be"}, - {file = "ansys_mapdl_reader-0.53.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0dacef48856116df3bf3e8079fad73e7ed95b6335346a1be098bcc81feeb576d"}, - {file = "ansys_mapdl_reader-0.53.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:15909da54a9a1a6d802fe9fcbaf64318a9312385a160ebd792a256bb3880e1c4"}, - {file = "ansys_mapdl_reader-0.53.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:dc7bd910388fe81f6e32cba868e653326758bd810844ae58ad11fdf995c51536"}, - {file = "ansys_mapdl_reader-0.53.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:31596066fdc635ed85e7f0cd61d8e8d8458839d561097fd201c78c0b249a04bf"}, - {file = "ansys_mapdl_reader-0.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e8f63cad6ea9d9646e4573f791c222305121fd6d71acf27ec254ee6cc8d5b62"}, - {file = "ansys_mapdl_reader-0.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:4ebfb62bde448f280aaeed034336ab2641fa3dfbf9ab256f000d15bf43df3cf3"}, +python-versions = "<4,>=3.7" +files = [ + {file = "ansys_mapdl_reader-0.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9739a53801c946069f9085a016255a4b1781e4eff50aaec250cadf9ea5af6cd8"}, + {file = "ansys_mapdl_reader-0.54.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1c27ad42915261b5f85344a4cbdec9f810badf6637c1d560626b90dd252a1c3"}, + {file = "ansys_mapdl_reader-0.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ef034967c669d6e86b038915e462d3429c27025140f3cfa3a1f4e3c9b809907"}, + {file = "ansys_mapdl_reader-0.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7f6c1d5ff8aa67838731f8d5b16b0eace355730c31152baf58e331abfb9abf6"}, + {file = "ansys_mapdl_reader-0.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fb381c1efab68df61b9d220b8bc05697576d9a2498bf5dceaf26e99616339aaa"}, + {file = "ansys_mapdl_reader-0.54.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54a12d3eac49d6416c07a40d9d22dea1c5ce61cd0d8bb5812c50517dc0ea38f6"}, + {file = "ansys_mapdl_reader-0.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f8f2548267e92ec8ca069136a05c1a2eb05db12a6f9d3ecb3b02b6f89520b1"}, + {file = "ansys_mapdl_reader-0.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:f44496cedde256eefa3cdfc56900e5eb18e5faff2e4591be584c34245f4ee67e"}, + {file = "ansys_mapdl_reader-0.54.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9568c12bb50efd1529720a3c45b59bf4a4ed64bb5e3aa180025751e625647752"}, + {file = "ansys_mapdl_reader-0.54.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ba6d811e854026130f618be038528578068a89bd533c66e470d297666be20c07"}, + {file = "ansys_mapdl_reader-0.54.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7766adaf69adbb1aa29dbdf81f27045b743cbd7f056552fb979bbaf8f9306b5e"}, + {file = "ansys_mapdl_reader-0.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb41ab0d904dbdf2c78abf67bc122586247f625e78ebc0bbd377983aceee25fb"}, + {file = "ansys_mapdl_reader-0.54.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6aa630bd7e5703146c78ce9824775b3f1990517c19484bc7fc8ee40f03043bdf"}, + {file = "ansys_mapdl_reader-0.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0e0a089ad81b68ee75c4e8f6c4a95b87200d9f942c3ae33046702e958fccb69b"}, + {file = "ansys_mapdl_reader-0.54.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6204dab63039c7f529c6979dce6a0e7ba83e04a5df65dfc68aee0bf5c4eec609"}, + {file = "ansys_mapdl_reader-0.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7c5fdec2ebc57c3a20c706fdaf8e3216d2f42e822bc3ed3dd05c63828f7dd5d"}, + {file = "ansys_mapdl_reader-0.54.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0b7b16d3d7d44d7bdf67d9da5eaaaa63c18cba0991bac55d23fe60d732522952"}, + {file = "ansys_mapdl_reader-0.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e49b9c84affbeec7c417d60739f5d487b39cca3eaf57eeeb321afa27fed17aab"}, + {file = "ansys_mapdl_reader-0.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7d8c13cf74f023bd331203b487291d759f853e9868686a443b4a22cb20d4bbe"}, + {file = "ansys_mapdl_reader-0.54.1.tar.gz", hash = "sha256:e9ae69f8c674e111ed46628f55296c25ef4ec1ec603eda9edb39bc49895dfe13"}, ] [package.dependencies] appdirs = ">=1.4.0" matplotlib = ">=3.0.0" -numpy = ">=1.16.0" +numpy = ">=1.16.0,<3" pyvista = ">=0.32.0" tqdm = ">=4.45.0" vtk = ">=9.0.0" @@ -455,13 +470,13 @@ clr-loader = ">=0.2.6,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.0.8" +version = "1.0.9" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.9" files = [ - {file = "ansys_sphinx_theme-1.0.8-py3-none-any.whl", hash = "sha256:c212239c44691f8d5a5b09b3b5dd4322edbd5935793420b6eadbde0d18af2286"}, - {file = "ansys_sphinx_theme-1.0.8.tar.gz", hash = "sha256:e7a6adf9072541b6e7492ea8ae399fb3228c6022612d9fc9df97c7c73ef78cc3"}, + {file = "ansys_sphinx_theme-1.0.9-py3-none-any.whl", hash = "sha256:583769355cf8a0d779ce7388bb66792a03f5f9f4ed0d8d8998469d2d2a563ada"}, + {file = "ansys_sphinx_theme-1.0.9.tar.gz", hash = "sha256:e5190aba2cc5dec0163d7b86d352c9d87a39f6b3e23cd7cea58cf154e0565d3f"}, ] [package.dependencies] @@ -812,100 +827,100 @@ css = ["tinycss2 (>=1.1.0,<1.3)"] [[package]] name = "cachetools" -version = "5.4.0" +version = "5.5.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, - {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.17.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, - {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, - {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, - {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, - {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, - {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, - {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, - {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, - {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, - {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, - {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, - {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, - {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, - {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, - {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, - {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -1079,66 +1094,87 @@ test = ["pytest"] [[package]] name = "contourpy" -version = "1.2.1" +version = "1.3.0" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.9" files = [ - {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, - {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, - {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, - {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, - {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, - {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, - {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, - {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, - {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, - {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, - {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, ] [package.dependencies] -numpy = ">=1.20" +numpy = ">=1.23" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] [[package]] name = "coverage" @@ -1369,13 +1405,13 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.0.1" +version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, ] [package.extras] @@ -1397,19 +1433,19 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.15.4" +version = "3.16.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, + {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, + {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "fonttools" @@ -1586,13 +1622,13 @@ files = [ [[package]] name = "google-api-core" -version = "2.19.1" +version = "2.19.2" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, - {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, + {file = "google_api_core-2.19.2-py3-none-any.whl", hash = "sha256:53ec0258f2837dd53bbd3d3df50f5359281b3cc13f800c941dd15a9b5a415af4"}, + {file = "google_api_core-2.19.2.tar.gz", hash = "sha256:ca07de7e8aa1c98a8bfca9321890ad2340ef7f2eb136e558cee68f24b94b0a8f"}, ] [package.dependencies] @@ -1609,13 +1645,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.141.0" +version = "2.145.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.141.0-py2.py3-none-any.whl", hash = "sha256:43c05322b91791204465291b3852718fae38d4f84b411d8be847c4f86882652a"}, - {file = "google_api_python_client-2.141.0.tar.gz", hash = "sha256:0f225b1f45d5a6f8c2a400f48729f5d6da9a81138e81e0478d61fdd8edf6563a"}, + {file = "google_api_python_client-2.145.0-py2.py3-none-any.whl", hash = "sha256:d74da1358f3f2d63daf3c6f26bd96d89652051183bc87cf10a56ceb2a70beb50"}, + {file = "google_api_python_client-2.145.0.tar.gz", hash = "sha256:8b84dde11aaccadc127e4846f5cd932331d804ea324e353131595e3f25376e97"}, ] [package.dependencies] @@ -1627,13 +1663,13 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.33.0" +version = "2.34.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.33.0-py2.py3-none-any.whl", hash = "sha256:8eff47d0d4a34ab6265c50a106a3362de6a9975bb08998700e389f857e4d39df"}, - {file = "google_auth-2.33.0.tar.gz", hash = "sha256:d6a52342160d7290e334b4d47ba390767e4438ad0d45b7630774533e82655b95"}, + {file = "google_auth-2.34.0-py2.py3-none-any.whl", hash = "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65"}, + {file = "google_auth-2.34.0.tar.gz", hash = "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc"}, ] [package.dependencies] @@ -1643,7 +1679,7 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +enterprise-cert = ["cryptography", "pyopenssl"] pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] @@ -1665,13 +1701,13 @@ httplib2 = ">=0.19.0" [[package]] name = "googleapis-common-protos" -version = "1.63.2" +version = "1.65.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, - {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, + {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, + {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, ] [package.dependencies] @@ -1682,61 +1718,61 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "grpcio" -version = "1.65.4" +version = "1.66.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.65.4-cp310-cp310-linux_armv7l.whl", hash = "sha256:0e85c8766cf7f004ab01aff6a0393935a30d84388fa3c58d77849fcf27f3e98c"}, - {file = "grpcio-1.65.4-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:e4a795c02405c7dfa8affd98c14d980f4acea16ea3b539e7404c645329460e5a"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d7b984a8dd975d949c2042b9b5ebcf297d6d5af57dcd47f946849ee15d3c2fb8"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644a783ce604a7d7c91412bd51cf9418b942cf71896344b6dc8d55713c71ce82"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5764237d751d3031a36fafd57eb7d36fd2c10c658d2b4057c516ccf114849a3e"}, - {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee40d058cf20e1dd4cacec9c39e9bce13fedd38ce32f9ba00f639464fcb757de"}, - {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4482a44ce7cf577a1f8082e807a5b909236bce35b3e3897f839f2fbd9ae6982d"}, - {file = "grpcio-1.65.4-cp310-cp310-win32.whl", hash = "sha256:66bb051881c84aa82e4f22d8ebc9d1704b2e35d7867757f0740c6ef7b902f9b1"}, - {file = "grpcio-1.65.4-cp310-cp310-win_amd64.whl", hash = "sha256:870370524eff3144304da4d1bbe901d39bdd24f858ce849b7197e530c8c8f2ec"}, - {file = "grpcio-1.65.4-cp311-cp311-linux_armv7l.whl", hash = "sha256:85e9c69378af02e483bc626fc19a218451b24a402bdf44c7531e4c9253fb49ef"}, - {file = "grpcio-1.65.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2bd672e005afab8bf0d6aad5ad659e72a06dd713020554182a66d7c0c8f47e18"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:abccc5d73f5988e8f512eb29341ed9ced923b586bb72e785f265131c160231d8"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:886b45b29f3793b0c2576201947258782d7e54a218fe15d4a0468d9a6e00ce17"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be952436571dacc93ccc7796db06b7daf37b3b56bb97e3420e6503dccfe2f1b4"}, - {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8dc9ddc4603ec43f6238a5c95400c9a901b6d079feb824e890623da7194ff11e"}, - {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ade1256c98cba5a333ef54636095f2c09e6882c35f76acb04412f3b1aa3c29a5"}, - {file = "grpcio-1.65.4-cp311-cp311-win32.whl", hash = "sha256:280e93356fba6058cbbfc6f91a18e958062ef1bdaf5b1caf46c615ba1ae71b5b"}, - {file = "grpcio-1.65.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2b819f9ee27ed4e3e737a4f3920e337e00bc53f9e254377dd26fc7027c4d558"}, - {file = "grpcio-1.65.4-cp312-cp312-linux_armv7l.whl", hash = "sha256:926a0750a5e6fb002542e80f7fa6cab8b1a2ce5513a1c24641da33e088ca4c56"}, - {file = "grpcio-1.65.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2a1d4c84d9e657f72bfbab8bedf31bdfc6bfc4a1efb10b8f2d28241efabfaaf2"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:17de4fda50967679677712eec0a5c13e8904b76ec90ac845d83386b65da0ae1e"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dee50c1b69754a4228e933696408ea87f7e896e8d9797a3ed2aeed8dbd04b74"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c34fc7562bdd169b77966068434a93040bfca990e235f7a67cdf26e1bd5c63"}, - {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:24a2246e80a059b9eb981e4c2a6d8111b1b5e03a44421adbf2736cc1d4988a8a"}, - {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18c10f0d054d2dce34dd15855fcca7cc44ec3b811139437543226776730c0f28"}, - {file = "grpcio-1.65.4-cp312-cp312-win32.whl", hash = "sha256:d72962788b6c22ddbcdb70b10c11fbb37d60ae598c51eb47ec019db66ccfdff0"}, - {file = "grpcio-1.65.4-cp312-cp312-win_amd64.whl", hash = "sha256:7656376821fed8c89e68206a522522317787a3d9ed66fb5110b1dff736a5e416"}, - {file = "grpcio-1.65.4-cp38-cp38-linux_armv7l.whl", hash = "sha256:4934077b33aa6fe0b451de8b71dabde96bf2d9b4cb2b3187be86e5adebcba021"}, - {file = "grpcio-1.65.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0cef8c919a3359847c357cb4314e50ed1f0cca070f828ee8f878d362fd744d52"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a925446e6aa12ca37114840d8550f308e29026cdc423a73da3043fd1603a6385"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf53e6247f1e2af93657e62e240e4f12e11ee0b9cef4ddcb37eab03d501ca864"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb34278e4ceb224c89704cd23db0d902e5e3c1c9687ec9d7c5bb4c150f86816"}, - {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e6cbdd107e56bde55c565da5fd16f08e1b4e9b0674851d7749e7f32d8645f524"}, - {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:626319a156b1f19513156a3b0dbfe977f5f93db63ca673a0703238ebd40670d7"}, - {file = "grpcio-1.65.4-cp38-cp38-win32.whl", hash = "sha256:3d1bbf7e1dd1096378bd83c83f554d3b93819b91161deaf63e03b7022a85224a"}, - {file = "grpcio-1.65.4-cp38-cp38-win_amd64.whl", hash = "sha256:a99e6dffefd3027b438116f33ed1261c8d360f0dd4f943cb44541a2782eba72f"}, - {file = "grpcio-1.65.4-cp39-cp39-linux_armv7l.whl", hash = "sha256:874acd010e60a2ec1e30d5e505b0651ab12eb968157cd244f852b27c6dbed733"}, - {file = "grpcio-1.65.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b07f36faf01fca5427d4aa23645e2d492157d56c91fab7e06fe5697d7e171ad4"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b81711bf4ec08a3710b534e8054c7dcf90f2edc22bebe11c1775a23f145595fe"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88fcabc332a4aef8bcefadc34a02e9ab9407ab975d2c7d981a8e12c1aed92aa1"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ba3e63108a8749994f02c7c0e156afb39ba5bdf755337de8e75eb685be244b"}, - {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8eb485801957a486bf5de15f2c792d9f9c897a86f2f18db8f3f6795a094b4bb2"}, - {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075f3903bc1749ace93f2b0664f72964ee5f2da5c15d4b47e0ab68e4f442c257"}, - {file = "grpcio-1.65.4-cp39-cp39-win32.whl", hash = "sha256:0a0720299bdb2cc7306737295d56e41ce8827d5669d4a3cd870af832e3b17c4d"}, - {file = "grpcio-1.65.4-cp39-cp39-win_amd64.whl", hash = "sha256:a146bc40fa78769f22e1e9ff4f110ef36ad271b79707577bf2a31e3e931141b9"}, - {file = "grpcio-1.65.4.tar.gz", hash = "sha256:2a4f476209acffec056360d3e647ae0e14ae13dcf3dfb130c227ae1c594cbe39"}, + {file = "grpcio-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:4877ba180591acdf127afe21ec1c7ff8a5ecf0fe2600f0d3c50e8c4a1cbc6492"}, + {file = "grpcio-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3750c5a00bd644c75f4507f77a804d0189d97a107eb1481945a0cf3af3e7a5ac"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a013c5fbb12bfb5f927444b477a26f1080755a931d5d362e6a9a720ca7dbae60"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1b24c23d51a1e8790b25514157d43f0a4dce1ac12b3f0b8e9f66a5e2c4c132f"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffb8ea674d68de4cac6f57d2498fef477cef582f1fa849e9f844863af50083"}, + {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:307b1d538140f19ccbd3aed7a93d8f71103c5d525f3c96f8616111614b14bf2a"}, + {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c17ebcec157cfb8dd445890a03e20caf6209a5bd4ac5b040ae9dbc59eef091d"}, + {file = "grpcio-1.66.1-cp310-cp310-win32.whl", hash = "sha256:ef82d361ed5849d34cf09105d00b94b6728d289d6b9235513cb2fcc79f7c432c"}, + {file = "grpcio-1.66.1-cp310-cp310-win_amd64.whl", hash = "sha256:292a846b92cdcd40ecca46e694997dd6b9be6c4c01a94a0dfb3fcb75d20da858"}, + {file = "grpcio-1.66.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:c30aeceeaff11cd5ddbc348f37c58bcb96da8d5aa93fed78ab329de5f37a0d7a"}, + {file = "grpcio-1.66.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a1e224ce6f740dbb6b24c58f885422deebd7eb724aff0671a847f8951857c26"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a66fe4dc35d2330c185cfbb42959f57ad36f257e0cc4557d11d9f0a3f14311df"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ba04659e4fce609de2658fe4dbf7d6ed21987a94460f5f92df7579fd5d0e22"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4573608e23f7e091acfbe3e84ac2045680b69751d8d67685ffa193a4429fedb1"}, + {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7e06aa1f764ec8265b19d8f00140b8c4b6ca179a6dc67aa9413867c47e1fb04e"}, + {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3885f037eb11f1cacc41f207b705f38a44b69478086f40608959bf5ad85826dd"}, + {file = "grpcio-1.66.1-cp311-cp311-win32.whl", hash = "sha256:97ae7edd3f3f91480e48ede5d3e7d431ad6005bfdbd65c1b56913799ec79e791"}, + {file = "grpcio-1.66.1-cp311-cp311-win_amd64.whl", hash = "sha256:cfd349de4158d797db2bd82d2020554a121674e98fbe6b15328456b3bf2495bb"}, + {file = "grpcio-1.66.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:a92c4f58c01c77205df6ff999faa008540475c39b835277fb8883b11cada127a"}, + {file = "grpcio-1.66.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fdb14bad0835914f325349ed34a51940bc2ad965142eb3090081593c6e347be9"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f03a5884c56256e08fd9e262e11b5cfacf1af96e2ce78dc095d2c41ccae2c80d"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2559692d8e7e245d456877a85ee41525f3ed425aa97eb7a70fc9a79df91a0"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca1be089fb4446490dd1135828bd42a7c7f8421e74fa581611f7afdf7ab761"}, + {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d639c939ad7c440c7b2819a28d559179a4508783f7e5b991166f8d7a34b52815"}, + {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9feb4e5ec8dc2d15709f4d5fc367794d69277f5d680baf1910fc9915c633524"}, + {file = "grpcio-1.66.1-cp312-cp312-win32.whl", hash = "sha256:7101db1bd4cd9b880294dec41a93fcdce465bdbb602cd8dc5bd2d6362b618759"}, + {file = "grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734"}, + {file = "grpcio-1.66.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:ecfe735e7a59e5a98208447293ff8580e9db1e890e232b8b292dc8bd15afc0d2"}, + {file = "grpcio-1.66.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4825a3aa5648010842e1c9d35a082187746aa0cdbf1b7a2a930595a94fb10fce"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f517fd7259fe823ef3bd21e508b653d5492e706e9f0ef82c16ce3347a8a5620c"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1fe60d0772831d96d263b53d83fb9a3d050a94b0e94b6d004a5ad111faa5b5b"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31a049daa428f928f21090403e5d18ea02670e3d5d172581670be006100db9ef"}, + {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f914386e52cbdeb5d2a7ce3bf1fdfacbe9d818dd81b6099a05b741aaf3848bb"}, + {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bff2096bdba686019fb32d2dde45b95981f0d1490e054400f70fc9a8af34b49d"}, + {file = "grpcio-1.66.1-cp38-cp38-win32.whl", hash = "sha256:aa8ba945c96e73de29d25331b26f3e416e0c0f621e984a3ebdb2d0d0b596a3b3"}, + {file = "grpcio-1.66.1-cp38-cp38-win_amd64.whl", hash = "sha256:161d5c535c2bdf61b95080e7f0f017a1dfcb812bf54093e71e5562b16225b4ce"}, + {file = "grpcio-1.66.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:d0cd7050397b3609ea51727b1811e663ffda8bda39c6a5bb69525ef12414b503"}, + {file = "grpcio-1.66.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0e6c9b42ded5d02b6b1fea3a25f036a2236eeb75d0579bfd43c0018c88bf0a3e"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c9f80f9fad93a8cf71c7f161778ba47fd730d13a343a46258065c4deb4b550c0"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dd67ed9da78e5121efc5c510f0122a972216808d6de70953a740560c572eb44"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48b0d92d45ce3be2084b92fb5bae2f64c208fea8ceed7fccf6a7b524d3c4942e"}, + {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d813316d1a752be6f5c4360c49f55b06d4fe212d7df03253dfdae90c8a402bb"}, + {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c9bebc6627873ec27a70fc800f6083a13c70b23a5564788754b9ee52c5aef6c"}, + {file = "grpcio-1.66.1-cp39-cp39-win32.whl", hash = "sha256:30a1c2cf9390c894c90bbc70147f2372130ad189cffef161f0432d0157973f45"}, + {file = "grpcio-1.66.1-cp39-cp39-win_amd64.whl", hash = "sha256:17663598aadbedc3cacd7bbde432f541c8e07d2496564e22b214b22c7523dac8"}, + {file = "grpcio-1.66.1.tar.gz", hash = "sha256:35334f9c9745add3e357e3372756fd32d925bd52c41da97f4dfdafbde0bf0ee2"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.65.4)"] +protobuf = ["grpcio-tools (>=1.66.1)"] [[package]] name = "grpcio-health-checking" @@ -1769,13 +1805,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.112.0" +version = "6.112.1" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.112.0-py3-none-any.whl", hash = "sha256:1e6adbd9534c0d691690b5006904327ea37c851d4e15262a22094aa77879e84d"}, - {file = "hypothesis-6.112.0.tar.gz", hash = "sha256:06ea8857e1e711a1a6f24154a3c8c4eab04b041993206aaa267f98b859fd6ef5"}, + {file = "hypothesis-6.112.1-py3-none-any.whl", hash = "sha256:93631b1498b20d2c205ed304cbd41d50e9c069d78a9c773c1324ca094c5e30ce"}, + {file = "hypothesis-6.112.1.tar.gz", hash = "sha256:b070d7a1bb9bd84706c31885c9aeddc138e2b36a9c112a91984f49501c567856"}, ] [package.dependencies] @@ -1802,13 +1838,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2024.1)"] [[package]] name = "identify" -version = "2.6.0" +version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, + {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, + {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, ] [package.extras] @@ -1816,15 +1852,18 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "imagesize" version = "1.4.1" @@ -1838,40 +1877,48 @@ files = [ [[package]] name = "importlib-metadata" -version = "8.2.0" +version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, - {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "importlib-resources" -version = "6.4.2" +version = "6.4.5" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.4.2-py3-none-any.whl", hash = "sha256:8bba8c54a8a3afaa1419910845fa26ebd706dc716dd208d9b158b4b6966f5c5c"}, - {file = "importlib_resources-6.4.2.tar.gz", hash = "sha256:6cbfbefc449cc6e2095dd184691b7a12a04f40bc75dd4c55d31c34f174cdf57a"}, + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -1956,21 +2003,21 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pa [[package]] name = "ipywidgets" -version = "8.1.3" +version = "8.1.5" description = "Jupyter interactive widgets" optional = false python-versions = ">=3.7" files = [ - {file = "ipywidgets-8.1.3-py3-none-any.whl", hash = "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2"}, - {file = "ipywidgets-8.1.3.tar.gz", hash = "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c"}, + {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, + {file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"}, ] [package.dependencies] comm = ">=0.1.3" ipython = ">=6.1.0" -jupyterlab-widgets = ">=3.0.11,<3.1.0" +jupyterlab-widgets = ">=3.0.12,<3.1.0" traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0.11,<4.1.0" +widgetsnbextension = ">=4.0.12,<4.1.0" [package.extras] test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] @@ -2185,13 +2232,13 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0,<9)", "pytest-console [[package]] name = "jupyter-server-proxy" -version = "4.3.0" +version = "4.4.0" description = "A Jupyter server extension to run additional processes and proxy to them that comes bundled JupyterLab extension to launch pre-defined processes." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server_proxy-4.3.0-py3-none-any.whl", hash = "sha256:0e664cf46ff8acd4c66b947ef33eb6e8a1a7bc3896ba47517ab8f24da5d198d7"}, - {file = "jupyter_server_proxy-4.3.0.tar.gz", hash = "sha256:d14db5044dfc2e672f80b75b34df2c3439efd6fc90a7999aa37b0d592075ce70"}, + {file = "jupyter_server_proxy-4.4.0-py3-none-any.whl", hash = "sha256:707b5c84810bb8863d50f6c6d50a386fec216149e11802b7d4c451b54a63a9a6"}, + {file = "jupyter_server_proxy-4.4.0.tar.gz", hash = "sha256:e5732eb9c810c0caa997f90a2f15f7d09af638e7eea9c67eb5c43e9c1f0e1157"}, ] [package.dependencies] @@ -2240,126 +2287,136 @@ files = [ [[package]] name = "jupyterlab-widgets" -version = "3.0.11" +version = "3.0.13" description = "Jupyter interactive widgets for JupyterLab" optional = false python-versions = ">=3.7" files = [ - {file = "jupyterlab_widgets-3.0.11-py3-none-any.whl", hash = "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0"}, - {file = "jupyterlab_widgets-3.0.11.tar.gz", hash = "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27"}, + {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, + {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, ] [[package]] name = "kiwisolver" -version = "1.4.5" +version = "1.4.7" description = "A fast implementation of the Cassowary constraint solver" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, + {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, ] [[package]] @@ -2533,179 +2590,192 @@ files = [ [[package]] name = "more-itertools" -version = "10.4.0" +version = "10.5.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.4.0.tar.gz", hash = "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923"}, - {file = "more_itertools-10.4.0-py3-none-any.whl", hash = "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27"}, + {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, + {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, ] [[package]] name = "msgpack" -version = "1.0.8" +version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, + {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, + {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, + {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, + {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, + {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, + {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, + {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, + {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, + {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, + {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] [[package]] name = "multidict" -version = "6.0.5" +version = "6.1.0" description = "multidict implementation" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + [[package]] name = "mypy" version = "1.11.2" @@ -3119,19 +3189,19 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.3" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, + {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" @@ -3334,24 +3404,24 @@ tests = ["hypothesis (==6.87.1)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)"] [[package]] name = "pyasn1" -version = "0.6.0" +version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, ] [[package]] name = "pyasn1-modules" -version = "0.4.0" +version = "0.4.1" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, - {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, + {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, + {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, ] [package.dependencies] @@ -3443,13 +3513,13 @@ files = [ [[package]] name = "pyparsing" -version = "3.1.2" +version = "3.1.4" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, + {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, + {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, ] [package.extras] @@ -3457,13 +3527,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -3692,120 +3762,120 @@ files = [ [[package]] name = "pyzmq" -version = "26.1.0" +version = "26.2.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.7" files = [ - {file = "pyzmq-26.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:263cf1e36862310bf5becfbc488e18d5d698941858860c5a8c079d1511b3b18e"}, - {file = "pyzmq-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d5c8b17f6e8f29138678834cf8518049e740385eb2dbf736e8f07fc6587ec682"}, - {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a95c2358fcfdef3374cb8baf57f1064d73246d55e41683aaffb6cfe6862917"}, - {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99de52b8fbdb2a8f5301ae5fc0f9e6b3ba30d1d5fc0421956967edcc6914242"}, - {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bcbfbab4e1895d58ab7da1b5ce9a327764f0366911ba5b95406c9104bceacb0"}, - {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77ce6a332c7e362cb59b63f5edf730e83590d0ab4e59c2aa5bd79419a42e3449"}, - {file = "pyzmq-26.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba0a31d00e8616149a5ab440d058ec2da621e05d744914774c4dde6837e1f545"}, - {file = "pyzmq-26.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8b88641384e84a258b740801cd4dbc45c75f148ee674bec3149999adda4a8598"}, - {file = "pyzmq-26.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2fa76ebcebe555cce90f16246edc3ad83ab65bb7b3d4ce408cf6bc67740c4f88"}, - {file = "pyzmq-26.1.0-cp310-cp310-win32.whl", hash = "sha256:fbf558551cf415586e91160d69ca6416f3fce0b86175b64e4293644a7416b81b"}, - {file = "pyzmq-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a7b8aab50e5a288c9724d260feae25eda69582be84e97c012c80e1a5e7e03fb2"}, - {file = "pyzmq-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:08f74904cb066e1178c1ec706dfdb5c6c680cd7a8ed9efebeac923d84c1f13b1"}, - {file = "pyzmq-26.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:46d6800b45015f96b9d92ece229d92f2aef137d82906577d55fadeb9cf5fcb71"}, - {file = "pyzmq-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5bc2431167adc50ba42ea3e5e5f5cd70d93e18ab7b2f95e724dd8e1bd2c38120"}, - {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3bb34bebaa1b78e562931a1687ff663d298013f78f972a534f36c523311a84d"}, - {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3f6329340cef1c7ba9611bd038f2d523cea79f09f9c8f6b0553caba59ec562"}, - {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:471880c4c14e5a056a96cd224f5e71211997d40b4bf5e9fdded55dafab1f98f2"}, - {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ce6f2b66799971cbae5d6547acefa7231458289e0ad481d0be0740535da38d8b"}, - {file = "pyzmq-26.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a1f6ea5b1d6cdbb8cfa0536f0d470f12b4b41ad83625012e575f0e3ecfe97f0"}, - {file = "pyzmq-26.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b45e6445ac95ecb7d728604bae6538f40ccf4449b132b5428c09918523abc96d"}, - {file = "pyzmq-26.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:94c4262626424683feea0f3c34951d39d49d354722db2745c42aa6bb50ecd93b"}, - {file = "pyzmq-26.1.0-cp311-cp311-win32.whl", hash = "sha256:a0f0ab9df66eb34d58205913f4540e2ad17a175b05d81b0b7197bc57d000e829"}, - {file = "pyzmq-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8efb782f5a6c450589dbab4cb0f66f3a9026286333fe8f3a084399149af52f29"}, - {file = "pyzmq-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f133d05aaf623519f45e16ab77526e1e70d4e1308e084c2fb4cedb1a0c764bbb"}, - {file = "pyzmq-26.1.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:3d3146b1c3dcc8a1539e7cc094700b2be1e605a76f7c8f0979b6d3bde5ad4072"}, - {file = "pyzmq-26.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d9270fbf038bf34ffca4855bcda6e082e2c7f906b9eb8d9a8ce82691166060f7"}, - {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:995301f6740a421afc863a713fe62c0aaf564708d4aa057dfdf0f0f56525294b"}, - {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7eca8b89e56fb8c6c26dd3e09bd41b24789022acf1cf13358e96f1cafd8cae3"}, - {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d4feb2e83dfe9ace6374a847e98ee9d1246ebadcc0cb765482e272c34e5820"}, - {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d4fafc2eb5d83f4647331267808c7e0c5722c25a729a614dc2b90479cafa78bd"}, - {file = "pyzmq-26.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:58c33dc0e185dd97a9ac0288b3188d1be12b756eda67490e6ed6a75cf9491d79"}, - {file = "pyzmq-26.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:68a0a1d83d33d8367ddddb3e6bb4afbb0f92bd1dac2c72cd5e5ddc86bdafd3eb"}, - {file = "pyzmq-26.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ae7c57e22ad881af78075e0cea10a4c778e67234adc65c404391b417a4dda83"}, - {file = "pyzmq-26.1.0-cp312-cp312-win32.whl", hash = "sha256:347e84fc88cc4cb646597f6d3a7ea0998f887ee8dc31c08587e9c3fd7b5ccef3"}, - {file = "pyzmq-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:9f136a6e964830230912f75b5a116a21fe8e34128dcfd82285aa0ef07cb2c7bd"}, - {file = "pyzmq-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4b7a989c8f5a72ab1b2bbfa58105578753ae77b71ba33e7383a31ff75a504c4"}, - {file = "pyzmq-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d416f2088ac8f12daacffbc2e8918ef4d6be8568e9d7155c83b7cebed49d2322"}, - {file = "pyzmq-26.1.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:ecb6c88d7946166d783a635efc89f9a1ff11c33d680a20df9657b6902a1d133b"}, - {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:471312a7375571857a089342beccc1a63584315188560c7c0da7e0a23afd8a5c"}, - {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6cea102ffa16b737d11932c426f1dc14b5938cf7bc12e17269559c458ac334"}, - {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec7248673ffc7104b54e4957cee38b2f3075a13442348c8d651777bf41aa45ee"}, - {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0614aed6f87d550b5cecb03d795f4ddbb1544b78d02a4bd5eecf644ec98a39f6"}, - {file = "pyzmq-26.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e8746ce968be22a8a1801bf4a23e565f9687088580c3ed07af5846580dd97f76"}, - {file = "pyzmq-26.1.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:7688653574392d2eaeef75ddcd0b2de5b232d8730af29af56c5adf1df9ef8d6f"}, - {file = "pyzmq-26.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8d4dac7d97f15c653a5fedcafa82626bd6cee1450ccdaf84ffed7ea14f2b07a4"}, - {file = "pyzmq-26.1.0-cp313-cp313-win32.whl", hash = "sha256:ccb42ca0a4a46232d716779421bbebbcad23c08d37c980f02cc3a6bd115ad277"}, - {file = "pyzmq-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e1e5d0a25aea8b691a00d6b54b28ac514c8cc0d8646d05f7ca6cb64b97358250"}, - {file = "pyzmq-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:fc82269d24860cfa859b676d18850cbb8e312dcd7eada09e7d5b007e2f3d9eb1"}, - {file = "pyzmq-26.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:416ac51cabd54f587995c2b05421324700b22e98d3d0aa2cfaec985524d16f1d"}, - {file = "pyzmq-26.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:ff832cce719edd11266ca32bc74a626b814fff236824aa1aeaad399b69fe6eae"}, - {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:393daac1bcf81b2a23e696b7b638eedc965e9e3d2112961a072b6cd8179ad2eb"}, - {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9869fa984c8670c8ab899a719eb7b516860a29bc26300a84d24d8c1b71eae3ec"}, - {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b3b8e36fd4c32c0825b4461372949ecd1585d326802b1321f8b6dc1d7e9318c"}, - {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3ee647d84b83509b7271457bb428cc347037f437ead4b0b6e43b5eba35fec0aa"}, - {file = "pyzmq-26.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:45cb1a70eb00405ce3893041099655265fabcd9c4e1e50c330026e82257892c1"}, - {file = "pyzmq-26.1.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:5cca7b4adb86d7470e0fc96037771981d740f0b4cb99776d5cb59cd0e6684a73"}, - {file = "pyzmq-26.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:91d1a20bdaf3b25f3173ff44e54b1cfbc05f94c9e8133314eb2962a89e05d6e3"}, - {file = "pyzmq-26.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c0665d85535192098420428c779361b8823d3d7ec4848c6af3abb93bc5c915bf"}, - {file = "pyzmq-26.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:96d7c1d35ee4a495df56c50c83df7af1c9688cce2e9e0edffdbf50889c167595"}, - {file = "pyzmq-26.1.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b281b5ff5fcc9dcbfe941ac5c7fcd4b6c065adad12d850f95c9d6f23c2652384"}, - {file = "pyzmq-26.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5384c527a9a004445c5074f1e20db83086c8ff1682a626676229aafd9cf9f7d1"}, - {file = "pyzmq-26.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:754c99a9840839375ee251b38ac5964c0f369306eddb56804a073b6efdc0cd88"}, - {file = "pyzmq-26.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9bdfcb74b469b592972ed881bad57d22e2c0acc89f5e8c146782d0d90fb9f4bf"}, - {file = "pyzmq-26.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bd13f0231f4788db619347b971ca5f319c5b7ebee151afc7c14632068c6261d3"}, - {file = "pyzmq-26.1.0-cp37-cp37m-win32.whl", hash = "sha256:c5668dac86a869349828db5fc928ee3f58d450dce2c85607067d581f745e4fb1"}, - {file = "pyzmq-26.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad875277844cfaeca7fe299ddf8c8d8bfe271c3dc1caf14d454faa5cdbf2fa7a"}, - {file = "pyzmq-26.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:65c6e03cc0222eaf6aad57ff4ecc0a070451e23232bb48db4322cc45602cede0"}, - {file = "pyzmq-26.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:038ae4ffb63e3991f386e7fda85a9baab7d6617fe85b74a8f9cab190d73adb2b"}, - {file = "pyzmq-26.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bdeb2c61611293f64ac1073f4bf6723b67d291905308a7de9bb2ca87464e3273"}, - {file = "pyzmq-26.1.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:61dfa5ee9d7df297c859ac82b1226d8fefaf9c5113dc25c2c00ecad6feeeb04f"}, - {file = "pyzmq-26.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3292d384537b9918010769b82ab3e79fca8b23d74f56fc69a679106a3e2c2cf"}, - {file = "pyzmq-26.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f9499c70c19ff0fbe1007043acb5ad15c1dec7d8e84ab429bca8c87138e8f85c"}, - {file = "pyzmq-26.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d3dd5523ed258ad58fed7e364c92a9360d1af8a9371e0822bd0146bdf017ef4c"}, - {file = "pyzmq-26.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baba2fd199b098c5544ef2536b2499d2e2155392973ad32687024bd8572a7d1c"}, - {file = "pyzmq-26.1.0-cp38-cp38-win32.whl", hash = "sha256:ddbb2b386128d8eca92bd9ca74e80f73fe263bcca7aa419f5b4cbc1661e19741"}, - {file = "pyzmq-26.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:79e45a4096ec8388cdeb04a9fa5e9371583bcb826964d55b8b66cbffe7b33c86"}, - {file = "pyzmq-26.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:add52c78a12196bc0fda2de087ba6c876ea677cbda2e3eba63546b26e8bf177b"}, - {file = "pyzmq-26.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c03bd7f3339ff47de7ea9ac94a2b34580a8d4df69b50128bb6669e1191a895"}, - {file = "pyzmq-26.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dcc37d9d708784726fafc9c5e1232de655a009dbf97946f117aefa38d5985a0f"}, - {file = "pyzmq-26.1.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a6ed52f0b9bf8dcc64cc82cce0607a3dfed1dbb7e8c6f282adfccc7be9781de"}, - {file = "pyzmq-26.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451e16ae8bea3d95649317b463c9f95cd9022641ec884e3d63fc67841ae86dfe"}, - {file = "pyzmq-26.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:906e532c814e1d579138177a00ae835cd6becbf104d45ed9093a3aaf658f6a6a"}, - {file = "pyzmq-26.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05bacc4f94af468cc82808ae3293390278d5f3375bb20fef21e2034bb9a505b6"}, - {file = "pyzmq-26.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:57bb2acba798dc3740e913ffadd56b1fcef96f111e66f09e2a8db3050f1f12c8"}, - {file = "pyzmq-26.1.0-cp39-cp39-win32.whl", hash = "sha256:f774841bb0e8588505002962c02da420bcfb4c5056e87a139c6e45e745c0e2e2"}, - {file = "pyzmq-26.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:359c533bedc62c56415a1f5fcfd8279bc93453afdb0803307375ecf81c962402"}, - {file = "pyzmq-26.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:7907419d150b19962138ecec81a17d4892ea440c184949dc29b358bc730caf69"}, - {file = "pyzmq-26.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b24079a14c9596846bf7516fe75d1e2188d4a528364494859106a33d8b48be38"}, - {file = "pyzmq-26.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59d0acd2976e1064f1b398a00e2c3e77ed0a157529779e23087d4c2fb8aaa416"}, - {file = "pyzmq-26.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:911c43a4117915203c4cc8755e0f888e16c4676a82f61caee2f21b0c00e5b894"}, - {file = "pyzmq-26.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10163e586cc609f5f85c9b233195554d77b1e9a0801388907441aaeb22841c5"}, - {file = "pyzmq-26.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:28a8b2abb76042f5fd7bd720f7fea48c0fd3e82e9de0a1bf2c0de3812ce44a42"}, - {file = "pyzmq-26.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bef24d3e4ae2c985034439f449e3f9e06bf579974ce0e53d8a507a1577d5b2ab"}, - {file = "pyzmq-26.1.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2cd0f4d314f4a2518e8970b6f299ae18cff7c44d4a1fc06fc713f791c3a9e3ea"}, - {file = "pyzmq-26.1.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fa25a620eed2a419acc2cf10135b995f8f0ce78ad00534d729aa761e4adcef8a"}, - {file = "pyzmq-26.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef3b048822dca6d231d8a8ba21069844ae38f5d83889b9b690bf17d2acc7d099"}, - {file = "pyzmq-26.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9a6847c92d9851b59b9f33f968c68e9e441f9a0f8fc972c5580c5cd7cbc6ee24"}, - {file = "pyzmq-26.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9b9305004d7e4e6a824f4f19b6d8f32b3578aad6f19fc1122aaf320cbe3dc83"}, - {file = "pyzmq-26.1.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:63c1d3a65acb2f9c92dce03c4e1758cc552f1ae5c78d79a44e3bb88d2fa71f3a"}, - {file = "pyzmq-26.1.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d36b8fffe8b248a1b961c86fbdfa0129dfce878731d169ede7fa2631447331be"}, - {file = "pyzmq-26.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67976d12ebfd61a3bc7d77b71a9589b4d61d0422282596cf58c62c3866916544"}, - {file = "pyzmq-26.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:998444debc8816b5d8d15f966e42751032d0f4c55300c48cc337f2b3e4f17d03"}, - {file = "pyzmq-26.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5c88b2f13bcf55fee78ea83567b9fe079ba1a4bef8b35c376043440040f7edb"}, - {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d906d43e1592be4b25a587b7d96527cb67277542a5611e8ea9e996182fae410"}, - {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b0c9942430d731c786545da6be96d824a41a51742e3e374fedd9018ea43106"}, - {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:314d11564c00b77f6224d12eb3ddebe926c301e86b648a1835c5b28176c83eab"}, - {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:093a1a3cae2496233f14b57f4b485da01b4ff764582c854c0f42c6dd2be37f3d"}, - {file = "pyzmq-26.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c397b1b450f749a7e974d74c06d69bd22dd362142f370ef2bd32a684d6b480c"}, - {file = "pyzmq-26.1.0.tar.gz", hash = "sha256:6c5aeea71f018ebd3b9115c7cb13863dd850e98ca6b9258509de1246461a7e7f"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"}, + {file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"}, + {file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"}, + {file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"}, + {file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"}, + {file = "pyzmq-26.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win32.whl", hash = "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd"}, + {file = "pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988"}, + {file = "pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940"}, + {file = "pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f"}, + {file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"}, ] [package.dependencies] @@ -4072,19 +4142,23 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "72.2.0" +version = "75.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-72.2.0-py3-none-any.whl", hash = "sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4"}, - {file = "setuptools-72.2.0.tar.gz", hash = "sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9"}, + {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, + {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, ] [package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "simpervisor" @@ -4527,18 +4601,19 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "trame" -version = "3.6.3" +version = "3.6.5" description = "Trame, a framework to build applications in plain Python" optional = false python-versions = "*" files = [ - {file = "trame-3.6.3-py3-none-any.whl", hash = "sha256:c99968dfc39ac898bf6213d558a245cf7020f7f8761c3a617c2e0af56a03c1ad"}, - {file = "trame-3.6.3.tar.gz", hash = "sha256:09c1193eee3a2b1ed5a3dc46db75d8ee1cd1f8da66e4614d583f0ecf02705fc0"}, + {file = "trame-3.6.5-py3-none-any.whl", hash = "sha256:58b5be29287d275dbd2be15b4e453c788361f640253ad153ca99fb3eb28e00f3"}, + {file = "trame-3.6.5.tar.gz", hash = "sha256:fc26a7067f94377e01263facb4cd40b111d799d970b5fc8e2aecb52e60378959"}, ] [package.dependencies] trame-client = ">=3,<4" trame-server = ">=3,<4" +wslink = ">=2.1.3" [[package]] name = "trame-client" @@ -4556,18 +4631,18 @@ test = ["Pillow", "pixelmatch", "pytest", "pytest-xprocess", "seleniumbase"] [[package]] name = "trame-server" -version = "3.0.3" +version = "3.2.0" description = "Internal server side implementation of trame" optional = false python-versions = "*" files = [ - {file = "trame-server-3.0.3.tar.gz", hash = "sha256:0261bfca4e2807406ae57b8bc5ce200286606ffd8c57d7b866bc7ab45be2398b"}, - {file = "trame_server-3.0.3-py3-none-any.whl", hash = "sha256:6bbce8a48c3bf2ba38fb679596414c66034f0301315da7040b073fc487649c20"}, + {file = "trame-server-3.2.0.tar.gz", hash = "sha256:85969d603ae084cc47678972948b1a6e255e8581ea4c7cb99c6b414425ac952f"}, + {file = "trame_server-3.2.0-py3-none-any.whl", hash = "sha256:7cf747a7e3399c3de630311675ef432403775e9ac49c11de2fd9a9a5f92702d7"}, ] [package.dependencies] more-itertools = "*" -wslink = ">=2,<3" +wslink = ">=2.2.1,<3" [[package]] name = "trame-vtk" @@ -4585,13 +4660,13 @@ trame-client = "*" [[package]] name = "trame-vuetify" -version = "2.6.2" +version = "2.7.1" description = "Vuetify widgets for trame" optional = false python-versions = "*" files = [ - {file = "trame-vuetify-2.6.2.tar.gz", hash = "sha256:38b6703527dd94fce31b18baf49fc0f74522f615a41268e8c9f26a2d789e7d07"}, - {file = "trame_vuetify-2.6.2-py3-none-any.whl", hash = "sha256:60582e1decbde75a439af40744d16f1a726ad2669cf7ade38291c5e43909be19"}, + {file = "trame-vuetify-2.7.1.tar.gz", hash = "sha256:48443910a3a53b9fa040e05b261fce9326fe95309b3798d49f0d6aec84cbfdf8"}, + {file = "trame_vuetify-2.7.1-py3-none-any.whl", hash = "sha256:c6bfe3be49ac8b7aa354e4e2c1d92f2c2e9680eea563f46146eef6968a66c2de"}, ] [package.dependencies] @@ -4610,13 +4685,13 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20240316" +version = "2.9.0.20240906" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, - {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, + {file = "types-python-dateutil-2.9.0.20240906.tar.gz", hash = "sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e"}, + {file = "types_python_dateutil-2.9.0.20240906-py3-none-any.whl", hash = "sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6"}, ] [[package]] @@ -4657,13 +4732,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -4674,13 +4749,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.3" +version = "20.26.4" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, + {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, + {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, ] [package.dependencies] @@ -4788,24 +4863,24 @@ test = ["websockets"] [[package]] name = "widgetsnbextension" -version = "4.0.11" +version = "4.0.13" description = "Jupyter interactive widgets for Jupyter Notebook" optional = false python-versions = ">=3.7" files = [ - {file = "widgetsnbextension-4.0.11-py3-none-any.whl", hash = "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36"}, - {file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"}, + {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, + {file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"}, ] [[package]] name = "wslink" -version = "2.1.2" +version = "2.2.1" description = "Python/JavaScript library for communicating over WebSocket" optional = false python-versions = "*" files = [ - {file = "wslink-2.1.2-py3-none-any.whl", hash = "sha256:d733e2116905d3b68411385bf01901b38cbd69cc29e069f85fc0fbd9702e6d2c"}, - {file = "wslink-2.1.2.tar.gz", hash = "sha256:b03831fb8f98f4196435e3057e0bc1f441adbab62d80c70f85ab5b6f4969cd68"}, + {file = "wslink-2.2.1-py3-none-any.whl", hash = "sha256:bfa1ce14b576f4a4ac4478e74c92a1445000f4fbf33923c98ffd1dc922c993e2"}, + {file = "wslink-2.2.1.tar.gz", hash = "sha256:cf79c7e3abe47f19af641c81f051ef97a2a2cbb96359cf243517afde16764b82"}, ] [package.dependencies] @@ -4817,101 +4892,103 @@ ssl = ["cryptography"] [[package]] name = "yarl" -version = "1.9.4" +version = "1.11.1" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, + {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, + {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, + {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, + {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, + {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, + {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, + {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, + {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, + {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, + {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, + {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, + {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, + {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, + {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, ] [package.dependencies] @@ -4920,18 +4997,22 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.20.0" +version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, - {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [extras] examples = ["ansys-dpf-composites", "ansys-mapdl-core", "ansys-mechanical-core", "matplotlib", "scipy"] @@ -4939,4 +5020,4 @@ examples = ["ansys-dpf-composites", "ansys-mapdl-core", "ansys-mechanical-core", [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "8528b628233a1c28d985edc6e5e09888e8ae62c32bdf7941bbf60b8f73d684a2" +content-hash = "89f022fb7f373e77f9052f99f0b50b8783774f047fc0e4a86c952bd469c03793" diff --git a/pyproject.toml b/pyproject.toml index 76c8194958..99c94d6b81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ numpy = ">=1.22" grpcio-health-checking = ">=1.43" packaging = ">=15.0" typing-extensions = ">=4.5.0" -ansys-api-acp = "^0.1.dev9" +ansys-api-acp = "==0.2.0.dev0" ansys-tools-path = ">=0" ansys-tools-local-product-launcher = ">=0.1" ansys-tools-filetransfer = ">=0.1" diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py index d1cae9bb9a..8f0e467db7 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import Any, Callable +from typing import Any, Callable, Optional __all__ = ["wrap_to_string_enum"] @@ -37,6 +37,7 @@ def wrap_to_string_enum( key_converter: Callable[[str], str] = lambda val: val, value_converter: Callable[[str], str] = lambda val: val.lower(), doc: str, + explicit_value_list: Optional[tuple[int, ...]] = None, ) -> tuple[StrEnum, Callable[[StrEnum], int], Callable[[int], StrEnum]]: """Create a string Enum with the same keys as the given protobuf Enum. @@ -54,6 +55,9 @@ def wrap_to_string_enum( to_pb_conversion_dict: dict[Any, int] = {} from_pb_conversion_dict: dict[int, Any] = {} for key, pb_value in proto_enum.items(): + if explicit_value_list is not None: + if pb_value not in explicit_value_list: + continue enum_key = key_converter(key) enum_value = value_converter(key) fields.append((enum_key, enum_value)) diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index 90b7fbeae8..73331838a0 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -31,7 +31,6 @@ lookup_table_column_type_pb2, mesh_query_pb2, modeling_ply_pb2, - ply_geometry_export_pb2, ply_material_pb2, rosette_pb2, sensor_pb2, @@ -357,7 +356,12 @@ (PlyGeometryExportFormat, ply_geometry_export_format_to_pb, _) = wrap_to_string_enum( "PlyGeometryExportFormat", - ply_geometry_export_pb2.ExportFormat, + enum_types_pb2.FileFormat, module=__name__, doc="Options for the file format of the ply geometry export.", + explicit_value_list=( + enum_types_pb2.FileFormat.STEP, + enum_types_pb2.FileFormat.IGES, + enum_types_pb2.FileFormat.STL, + ), ) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 5c7e4fd573..421b58cd62 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -39,6 +39,7 @@ cylindrical_selection_rule_pb2_grpc, edge_set_pb2_grpc, element_set_pb2_grpc, + enum_types_pb2, fabric_pb2_grpc, geometrical_selection_rule_pb2_grpc, lookup_table_1d_pb2_grpc, @@ -133,10 +134,15 @@ FeFormat, fe_format_to_pb, _ = wrap_to_string_enum( "FeFormat", - model_pb2.Format, + enum_types_pb2.FileFormat, module=__name__, value_converter=lambda val: val.lower().replace("_", ":"), doc="Options for the format of the FE file.", + explicit_value_list=( + enum_types_pb2.FileFormat.ANSYS_H5, + enum_types_pb2.FileFormat.ANSYS_CDB, + enum_types_pb2.FileFormat.ANSYS_DAT, + ), ) IgnorableEntity, ignorable_entity_to_pb, _ = wrap_to_string_enum( "IgnorableEntity", From 5b48635c557d2ecb8a6ab65694c2b6fd32598059 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Fri, 16 Aug 2024 17:36:57 +0200 Subject: [PATCH 05/96] Drop support for Python 3.9 --- .github/workflows/ci_cd.yml | 6 +- .github/workflows/nightly.yml | 2 +- .pre-commit-config.yaml | 2 +- poetry.lock | 172 +++++++++++++++------------------- pyproject.toml | 5 +- 5 files changed, 84 insertions(+), 103 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 61d8ac80c3..f258db0ca1 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -93,7 +93,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] should-release: - ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags') }} exclude: @@ -113,7 +113,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -201,7 +201,7 @@ jobs: - name: Benchmarks working-directory: tests/benchmarks run: | - poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --build-benchmark-image --benchmark-json benchmark_output.json --benchmark-group-by=fullname ${{ (matrix.python-version == '3.9' && github.ref == 'refs/heads/main') && ' ' || '--validate-benchmarks-only' }} + poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --build-benchmark-image --benchmark-json benchmark_output.json --benchmark-group-by=fullname ${{ (matrix.python-version == '3.10' && github.ref == 'refs/heads/main') && ' ' || '--validate-benchmarks-only' }} env: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} IMAGE_NAME: ${{ env.DOCKER_IMAGE_NAME }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 9ca0cfc66d..fb38631895 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] timeout-minutes: 30 steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index acdf818a3c..d7380ff728 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: rev: v3.16.0 hooks: - id: pyupgrade - args: [--py39-plus] + args: [--py310-plus] - repo: https://github.com/psf/black rev: "24.4.2" # when changed, also update the version in blacken-docs diff --git a/poetry.lock b/poetry.lock index 09f6bfd879..d9a07ca013 100644 --- a/poetry.lock +++ b/poetry.lock @@ -157,13 +157,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alabaster" -version = "0.7.16" +version = "1.0.0" description = "A light, configurable Sphinx theme" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, - {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, + {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, + {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, ] [[package]] @@ -1898,28 +1898,6 @@ perf = ["ipython"] test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] -[[package]] -name = "importlib-resources" -version = "6.4.5" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, - {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] -type = ["pytest-mypy"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1966,13 +1944,13 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio [[package]] name = "ipython" -version = "8.18.1" +version = "8.27.0" description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, - {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, + {file = "ipython-8.27.0-py3-none-any.whl", hash = "sha256:f68b3cb8bde357a5d7adc9598d57e22a45dfbea19eb6b98286fa3b288c9cd55c"}, + {file = "ipython-8.27.0.tar.gz", hash = "sha256:0b99a2dc9f15fd68692e898e5568725c6d49c527d36a9fb5960ffbdeaa82ff7e"}, ] [package.dependencies] @@ -1981,25 +1959,26 @@ decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" -traitlets = ">=5" -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} +traitlets = ">=5.13.0" +typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] kernel = ["ipykernel"] +matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "ipywidgets" @@ -2138,7 +2117,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" python-dateutil = ">=2.8.2" pyzmq = ">=23.0" @@ -2243,7 +2221,6 @@ files = [ [package.dependencies] aiohttp = "*" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jupyter-server = ">=1.24.0" simpervisor = ">=1.0.0" tornado = ">=6.1.0" @@ -2552,7 +2529,6 @@ files = [ contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" -importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} kiwisolver = ">=1.3.1" numpy = ">=1.23" packaging = ">=20.0" @@ -2871,7 +2847,6 @@ files = [ beautifulsoup4 = "*" bleach = "!=5.0.0" defusedxml = "*" -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} jinja2 = ">=3.0" jupyter-core = ">=4.7" jupyterlab-pygments = "*" @@ -3389,18 +3364,18 @@ files = [ [[package]] name = "pyansys-tools-versioning" -version = "0.5.0" +version = "0.6.0" description = "PyAnsys Tools Versioning." optional = false -python-versions = ">=3.9,<4" +python-versions = "<4,>=3.10" files = [ - {file = "pyansys_tools_versioning-0.5.0-py3-none-any.whl", hash = "sha256:5dcac7272cd70a034ac100094535f1e420951d06f00b9dacf6597366a6f50825"}, - {file = "pyansys_tools_versioning-0.5.0.tar.gz", hash = "sha256:ed2266f2919a8022ddfd42e3c8d4ccfcaf575b730a09173d2b847eb919489d7f"}, + {file = "pyansys_tools_versioning-0.6.0-py3-none-any.whl", hash = "sha256:a7191203ccd89ce86a5413e268b3a51127a5b9f5117dba909422bcfdf6e7f81f"}, + {file = "pyansys_tools_versioning-0.6.0.tar.gz", hash = "sha256:582d430c2325aa5f9fea64abdfb77c14dc5153e814e813d4a37f0f88531e6e41"}, ] [package.extras] -doc = ["Sphinx (==7.2.6)", "Sphinx-copybutton (==0.5.2)", "ansys_sphinx_theme (==0.12.1)", "numpydoc (==1.6.0)", "sphinx-autoapi (==3.0.0)"] -tests = ["hypothesis (==6.87.1)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)"] +doc = ["Sphinx (==8.0.2)", "Sphinx-copybutton (==0.5.2)", "ansys_sphinx_theme[autoapi] (==1.0.3)", "numpydoc (==1.8.0)"] +tests = ["hypothesis (==6.111.0)", "pytest (==8.3.2)", "pytest-cov (==5.0.0)"] [[package]] name = "pyasn1" @@ -4070,45 +4045,53 @@ pyasn1 = ">=0.1.3" [[package]] name = "scipy" -version = "1.13.1" +version = "1.14.1" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.9" -files = [ - {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, - {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, - {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, - {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, - {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, - {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, - {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, - {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, - {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, - {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, - {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, ] [package.dependencies] -numpy = ">=1.22.4,<2.3" +numpy = ">=1.23.5,<2.3" [package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "scooby" @@ -4231,22 +4214,21 @@ files = [ [[package]] name = "sphinx" -version = "7.4.7" +version = "8.0.2" description = "Python documentation generator" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, - {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, + {file = "sphinx-8.0.2-py3-none-any.whl", hash = "sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"}, + {file = "sphinx-8.0.2.tar.gz", hash = "sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b"}, ] [package.dependencies] -alabaster = ">=0.7.14,<0.8.0" +alabaster = ">=0.7.14" babel = ">=2.13" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} docutils = ">=0.20,<0.22" imagesize = ">=1.3" -importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" @@ -4262,27 +4244,27 @@ tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +lint = ["flake8 (>=6.0)", "mypy (==1.11.0)", "pytest (>=6.0)", "ruff (==0.5.5)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240520)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20240724)", "types-requests (>=2.30.0)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-autodoc-typehints" -version = "2.3.0" +version = "2.4.1" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "sphinx_autodoc_typehints-2.3.0-py3-none-any.whl", hash = "sha256:3098e2c6d0ba99eacd013eb06861acc9b51c6e595be86ab05c08ee5506ac0c67"}, - {file = "sphinx_autodoc_typehints-2.3.0.tar.gz", hash = "sha256:535c78ed2d6a1bad393ba9f3dfa2602cf424e2631ee207263e07874c38fde084"}, + {file = "sphinx_autodoc_typehints-2.4.1-py3-none-any.whl", hash = "sha256:af37abb816ebd2cf56c7a8174fd2f34d0f2f84fbf58265f89429ae107212fe6f"}, + {file = "sphinx_autodoc_typehints-2.4.1.tar.gz", hash = "sha256:cfe410920cecf08ade046bb387b0007edb83e992de59686c62d194c762f1e45c"}, ] [package.dependencies] -sphinx = ">=7.3.5" +sphinx = ">=8.0.2" [package.extras] -docs = ["furo (>=2024.1.29)"] +docs = ["furo (>=2024.8.6)"] numpy = ["nptyping (>=2.5)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.4.4)", "defusedxml (>=0.7.1)", "diff-cover (>=9)", "pytest (>=8.1.1)", "pytest-cov (>=5)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.11)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "defusedxml (>=0.7.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "sphobjinv (>=2.3.1.1)", "typing-extensions (>=4.12.2)"] [[package]] name = "sphinx-copybutton" @@ -5019,5 +5001,5 @@ examples = ["ansys-dpf-composites", "ansys-mapdl-core", "ansys-mechanical-core", [metadata] lock-version = "2.0" -python-versions = ">=3.9,<3.13" -content-hash = "89f022fb7f373e77f9052f99f0b50b8783774f047fc0e4a86c952bd469c03793" +python-versions = ">=3.10,<3.13" +content-hash = "18c93570d47795eefec3cb312ff1cb5d1d89b55d4c1e5d84599dccd6d23d53d2" diff --git a/pyproject.toml b/pyproject.toml index 99c94d6b81..28ee3abea8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ include = ["src/**/docker-compose.yaml"] # Less than critical but helpful classifiers = [ "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -33,7 +32,7 @@ Issues = "https://github.com/ansys/pyacp/issues" Releases = "https://github.com/ansys/pyacp/releases" [tool.poetry.dependencies] -python = ">=3.9,<3.13" +python = ">=3.10,<3.13" numpy = ">=1.22" grpcio-health-checking = ">=1.43" packaging = ">=15.0" @@ -139,7 +138,7 @@ ignore-words-list = 'ans,uptodate,notuptodate,eyt' quiet-level = 3 [tool.mypy] -python_version = "3.9" +python_version = "3.10" mypy_path = "$MYPY_CONFIG_FILE_DIR/src:$MYPY_CONFIG_FILE_DIR/tests" disable_error_code = "type-abstract" show_error_context = true From 2d4a3b5af01b64e7cb6ab2bde6daa6c28113f165 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 20 Aug 2024 17:28:07 +0200 Subject: [PATCH 06/96] Update pre-commit hooks --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d7380ff728..3eb20eccb2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v3.16.0 + rev: v3.17.0 hooks: - id: pyupgrade args: [--py310-plus] - repo: https://github.com/psf/black - rev: "24.4.2" # when changed, also update the version in blacken-docs + rev: "24.8.0" # when changed, also update the version in blacken-docs hooks: - id: black @@ -14,7 +14,7 @@ repos: rev: 1.18.0 hooks: - id: blacken-docs - additional_dependencies: [black==24.4.2] + additional_dependencies: [black==24.8.0] - repo: https://github.com/pycqa/isort rev: "5.13.2" @@ -22,7 +22,7 @@ repos: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: "7.1.0" + rev: "7.1.1" hooks: - id: flake8 @@ -61,7 +61,7 @@ repos: ] - repo: https://github.com/ansys/pre-commit-hooks - rev: v0.3.1 + rev: v0.4.3 hooks: - id: add-license-headers args: ["--start_year", "2022"] From d099b3b4bb80a3461f7f174a6c2944aa13b10a05 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 17 Sep 2024 11:36:11 +0200 Subject: [PATCH 07/96] Run and fix pre-commit hooks --- src/ansys/acp/core/_model_printer.py | 5 ++--- src/ansys/acp/core/_server/direct.py | 8 ++++---- src/ansys/acp/core/_server/docker_compose.py | 9 ++++----- .../_grpc_helpers/edge_property_list.py | 6 +++--- .../core/_tree_objects/_grpc_helpers/enum_wrapper.py | 5 +++-- .../_grpc_helpers/linked_object_helpers.py | 3 +-- .../_grpc_helpers/linked_object_list.py | 4 ++-- .../acp/core/_tree_objects/_grpc_helpers/mapping.py | 6 +++--- .../_tree_objects/_grpc_helpers/property_helper.py | 3 ++- src/ansys/acp/core/_tree_objects/_object_cache.py | 6 +++--- src/ansys/acp/core/_tree_objects/base.py | 6 +++--- .../acp/core/_tree_objects/linked_selection_rule.py | 3 ++- src/ansys/acp/core/_tree_objects/modeling_ply.py | 4 ++-- src/ansys/acp/core/_tree_objects/stackup.py | 4 ++-- src/ansys/acp/core/_tree_objects/sublaminate.py | 4 ++-- src/ansys/acp/core/_tree_objects/virtual_geometry.py | 4 ++-- src/ansys/acp/core/_utils/array_conversions.py | 12 ++++++------ src/ansys/acp/core/_workflow.py | 11 ++++++----- tests/unittests/common/linked_object_list_tester.py | 3 ++- tests/unittests/common/tree_object_tester.py | 4 ++-- tests/unittests/helpers.py | 4 ++-- tests/unittests/test_material.py | 3 ++- type_checks/add_methods.py | 3 ++- type_checks/create_methods.py | 2 +- 24 files changed, 63 insertions(+), 59 deletions(-) diff --git a/src/ansys/acp/core/_model_printer.py b/src/ansys/acp/core/_model_printer.py index 8c3c38ac34..61f43ead20 100644 --- a/src/ansys/acp/core/_model_printer.py +++ b/src/ansys/acp/core/_model_printer.py @@ -21,7 +21,6 @@ # SOFTWARE. import os -from typing import Optional from ._tree_objects.model import Model from ._utils.visualization import _replace_underscores_and_capitalize @@ -40,11 +39,11 @@ class Node: Children of the node. """ - def __init__(self, label: str, children: Optional[list["Node"]] = None): + def __init__(self, label: str, children: list["Node"] | None = None): self.label = label self.children: list["Node"] = children if children else [] - def __str__(self, level: Optional[int] = 0) -> str: + def __str__(self, level: int | None = 0) -> str: assert level is not None four_spaces = " " ret = four_spaces * level + self.label + os.linesep diff --git a/src/ansys/acp/core/_server/direct.py b/src/ansys/acp/core/_server/direct.py index f3be053f28..3c5dafb24e 100644 --- a/src/ansys/acp/core/_server/direct.py +++ b/src/ansys/acp/core/_server/direct.py @@ -23,7 +23,7 @@ import dataclasses import os import subprocess -from typing import Optional, TextIO, Union +from typing import TextIO import grpc @@ -48,7 +48,7 @@ def _get_latest_ansys_installation() -> str: if not installations: raise ValueError("No Ansys installation found.") - def sort_key(version_nr: int) -> Union[int, float]: + def sort_key(version_nr: int) -> int | float: # prefer regular over student installs if version_nr < 0: return abs(version_nr) - 0.5 @@ -117,7 +117,7 @@ def start(self) -> None: text=True, ) - def stop(self, *, timeout: Optional[float] = None) -> None: + def stop(self, *, timeout: float | None = None) -> None: self._process.terminate() try: self._process.wait(timeout=timeout) @@ -127,7 +127,7 @@ def stop(self, *, timeout: Optional[float] = None) -> None: self._stdout.close() self._stderr.close() - def check(self, timeout: Optional[float] = None) -> bool: + def check(self, timeout: float | None = None) -> bool: channel = grpc.insecure_channel(self.urls[ServerKey.MAIN]) return check_grpc_health(channel=channel, timeout=timeout) diff --git a/src/ansys/acp/core/_server/docker_compose.py b/src/ansys/acp/core/_server/docker_compose.py index 78df5c0688..091825f441 100644 --- a/src/ansys/acp/core/_server/docker_compose.py +++ b/src/ansys/acp/core/_server/docker_compose.py @@ -30,7 +30,6 @@ import os import pathlib import subprocess -from typing import Optional import uuid import grpc @@ -85,7 +84,7 @@ class DockerComposeLaunchConfig: default=False, metadata={METADATA_KEY_DOC: "If true, keep the volume after docker compose is stopped."}, ) - compose_file: Optional[str] = dataclasses.field( + compose_file: str | None = dataclasses.field( default=None, metadata={ METADATA_KEY_DOC: ( @@ -134,7 +133,7 @@ def __init__(self, *, config: DockerComposeLaunchConfig): self._keep_volume = config.keep_volume if config.compose_file is not None: - self._compose_file: Optional[pathlib.Path] = pathlib.Path(config.compose_file) + self._compose_file: pathlib.Path | None = pathlib.Path(config.compose_file) else: self._compose_file = None @@ -193,7 +192,7 @@ def start(self) -> None: cmd, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) - def stop(self, *, timeout: Optional[float] = None) -> None: + def stop(self, *, timeout: float | None = None) -> None: # The compose file needs to be passed for all commands with docker-compose 1.X. # With docker-compose 2.X, this no longer seems to be necessary. with self._get_compose_file() as compose_file: @@ -211,7 +210,7 @@ def stop(self, *, timeout: Optional[float] = None) -> None: cmd.append("--volumes") subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - def check(self, timeout: Optional[float] = None) -> bool: + def check(self, timeout: float | None = None) -> bool: for url in self.urls.values(): channel = grpc.insecure_channel(url) if not check_grpc_health(channel=channel, timeout=timeout): diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py index 032abbb79f..3f51c5c438 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py @@ -22,13 +22,13 @@ from __future__ import annotations -from collections.abc import Iterable, Iterator, MutableSequence +from collections.abc import Callable, Iterable, Iterator, MutableSequence import sys import textwrap -from typing import Any, Callable, Protocol, TypeVar, cast, overload +from typing import Any, Concatenate, Protocol, TypeVar, cast, overload from google.protobuf.message import Message -from typing_extensions import Concatenate, ParamSpec, Self +from typing_extensions import ParamSpec, Self from .._object_cache import ObjectCacheMixin, constructor_with_cache from ..base import CreatableTreeObject diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py index 8f0e467db7..eeb9d6ea86 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import Any, Callable, Optional +from collections.abc import Callable +from typing import Any __all__ = ["wrap_to_string_enum"] @@ -37,7 +38,7 @@ def wrap_to_string_enum( key_converter: Callable[[str], str] = lambda val: val, value_converter: Callable[[str], str] = lambda val: val.lower(), doc: str, - explicit_value_list: Optional[tuple[int, ...]] = None, + explicit_value_list: tuple[int, ...] | None = None, ) -> tuple[StrEnum, Callable[[StrEnum], int], Callable[[int], StrEnum]]: """Create a string Enum with the same keys as the given protobuf Enum. diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py index bf4204cb4d..fd8b5d86d1 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py @@ -21,7 +21,6 @@ # SOFTWARE. from collections.abc import Iterable -from typing import Union from google.protobuf.descriptor import FieldDescriptor from google.protobuf.message import Message @@ -39,7 +38,7 @@ def unlink_objects(pb_object: Message) -> None: def linked_path_fields( pb_object: Message, -) -> Iterable[tuple[Message, FieldDescriptor, Union[ResourcePath, CollectionPath]]]: +) -> Iterable[tuple[Message, FieldDescriptor, ResourcePath | CollectionPath]]: """Get all linked paths from a protobuf object. Get tuples (parent_message, field_descriptor, {resource_path or collection_path}) diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py index 3bad490829..4bd4b5d7f2 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py @@ -22,10 +22,10 @@ from __future__ import annotations -from collections.abc import Iterable, Iterator, MutableSequence +from collections.abc import Callable, Iterable, Iterator, MutableSequence from functools import partial import sys -from typing import Any, Callable, TypeVar, cast, overload +from typing import Any, TypeVar, cast, overload from grpc import Channel import numpy as np diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py index c5590d27a9..5cb8353b74 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py @@ -22,11 +22,11 @@ from __future__ import annotations -from collections.abc import Iterator -from typing import Any, Callable, Generic, TypeVar +from collections.abc import Callable, Iterator +from typing import Any, Concatenate, Generic, TypeVar from grpc import Channel -from typing_extensions import Concatenate, ParamSpec, Self +from typing_extensions import ParamSpec, Self from ansys.api.acp.v0.base_pb2 import CollectionPath, DeleteRequest, ListRequest diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py index 0c628d51a0..c6300c812f 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py @@ -27,8 +27,9 @@ """ from __future__ import annotations +from collections.abc import Callable from functools import reduce -from typing import Any, Callable, TypeVar +from typing import Any, TypeVar from google.protobuf.message import Message diff --git a/src/ansys/acp/core/_tree_objects/_object_cache.py b/src/ansys/acp/core/_tree_objects/_object_cache.py index 63ee2fa388..7c4dbcab2c 100644 --- a/src/ansys/acp/core/_tree_objects/_object_cache.py +++ b/src/ansys/acp/core/_tree_objects/_object_cache.py @@ -20,11 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from collections.abc import Iterable -from typing import Any, Callable, TypeVar +from collections.abc import Callable, Iterable +from typing import Any, Concatenate, TypeAlias, TypeVar from weakref import WeakValueDictionary -from typing_extensions import Concatenate, ParamSpec, Self, TypeAlias +from typing_extensions import ParamSpec, Self __all__ = ["ObjectCacheMixin", "constructor_with_cache"] diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index 7f89e02d73..070c598857 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -24,16 +24,16 @@ from __future__ import annotations from abc import abstractmethod -from collections.abc import Iterable +from collections.abc import Callable, Iterable from dataclasses import dataclass from functools import wraps import typing -from typing import Any, Callable, Generic, TypeVar, cast +from typing import Any, Concatenate, Generic, TypeAlias, TypeVar, cast from grpc import Channel from packaging.version import Version from packaging.version import parse as parse_version -from typing_extensions import Concatenate, ParamSpec, Self, TypeAlias +from typing_extensions import ParamSpec, Self from ansys.api.acp.v0.base_pb2 import CollectionPath, DeleteRequest, GetRequest, ResourcePath diff --git a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py index f4df67d4fc..34a67a731f 100644 --- a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py @@ -22,8 +22,9 @@ from __future__ import annotations +from collections.abc import Callable import typing -from typing import Callable, Union +from typing import Union from typing_extensions import Self diff --git a/src/ansys/acp/core/_tree_objects/modeling_ply.py b/src/ansys/acp/core/_tree_objects/modeling_ply.py index 97d9160e65..6ad2dd5140 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/modeling_ply.py @@ -22,9 +22,9 @@ from __future__ import annotations -from collections.abc import Container, Iterable +from collections.abc import Callable, Container, Iterable import dataclasses -from typing import Any, Callable +from typing import Any import numpy as np from typing_extensions import Self diff --git a/src/ansys/acp/core/_tree_objects/stackup.py b/src/ansys/acp/core/_tree_objects/stackup.py index 0bd55ec11e..a9a2936453 100644 --- a/src/ansys/acp/core/_tree_objects/stackup.py +++ b/src/ansys/acp/core/_tree_objects/stackup.py @@ -22,8 +22,8 @@ from __future__ import annotations -from collections.abc import Iterable, Sequence -from typing import Any, Callable +from collections.abc import Callable, Iterable, Sequence +from typing import Any from ansys.api.acp.v0 import stackup_pb2, stackup_pb2_grpc diff --git a/src/ansys/acp/core/_tree_objects/sublaminate.py b/src/ansys/acp/core/_tree_objects/sublaminate.py index 7fe1b01fe4..d2877f9750 100644 --- a/src/ansys/acp/core/_tree_objects/sublaminate.py +++ b/src/ansys/acp/core/_tree_objects/sublaminate.py @@ -22,9 +22,9 @@ from __future__ import annotations -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence import typing -from typing import Any, Callable, Union, get_args +from typing import Any, Union, get_args from ansys.api.acp.v0 import sublaminate_pb2, sublaminate_pb2_grpc diff --git a/src/ansys/acp/core/_tree_objects/virtual_geometry.py b/src/ansys/acp/core/_tree_objects/virtual_geometry.py index 2a25ace61f..3ea22b7ee4 100644 --- a/src/ansys/acp/core/_tree_objects/virtual_geometry.py +++ b/src/ansys/acp/core/_tree_objects/virtual_geometry.py @@ -22,9 +22,9 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Callable, Iterable import typing -from typing import Any, Callable +from typing import Any from ansys.api.acp.v0 import base_pb2, virtual_geometry_pb2, virtual_geometry_pb2_grpc diff --git a/src/ansys/acp/core/_utils/array_conversions.py b/src/ansys/acp/core/_utils/array_conversions.py index 036670705f..e6d771a46d 100644 --- a/src/ansys/acp/core/_utils/array_conversions.py +++ b/src/ansys/acp/core/_utils/array_conversions.py @@ -21,7 +21,7 @@ # SOFTWARE. from collections.abc import Collection -from typing import Any, Union, overload +from typing import Any, overload import numpy as np import numpy.typing as npt @@ -40,7 +40,7 @@ def to_1D_int_array(data: Collection[int]) -> IntArray: return IntArray(shape=[len(data)], data=tuple(data)) -def to_tuple_from_1D_array(array: Union[IntArray, DoubleArray]) -> tuple[Any, ...]: +def to_tuple_from_1D_array(array: IntArray | DoubleArray) -> tuple[Any, ...]: """Convert a 1D IntArray or DoubleArray protobuf message to a tuple.""" if not len(array.shape) == 1: raise RuntimeError(f"Cannot convert {len(array.shape)}-dimensional array to tuple!") @@ -66,8 +66,8 @@ def to_numpy(array_pb: DoubleArray) -> npt.NDArray[np.float64]: ... def to_numpy( - array_pb: Union[IntArray, Int32Array, DoubleArray] -) -> Union[npt.NDArray[np.int64], npt.NDArray[np.int32], npt.NDArray[np.float64]]: + array_pb: IntArray | Int32Array | DoubleArray, +) -> npt.NDArray[np.int64] | npt.NDArray[np.int32] | npt.NDArray[np.float64]: """Convert a protubuf array message to a numpy array.""" dtype = { IntArray: np.int64, @@ -79,8 +79,8 @@ def to_numpy( def dataarray_to_numpy( array_pb: DataArray, - dtype: Union[type[np.int32], type[np.int64], type[np.float64]], -) -> Union[npt.NDArray[np.int64], npt.NDArray[np.int32], npt.NDArray[np.float64]]: + dtype: type[np.int32] | type[np.int64] | type[np.float64], +) -> npt.NDArray[np.int64] | npt.NDArray[np.int32] | npt.NDArray[np.float64]: """Convert a DataArray protobuf message to a numpy array.""" data_array_attribute = array_pb.WhichOneof("data") if data_array_attribute is None: diff --git a/src/ansys/acp/core/_workflow.py b/src/ansys/acp/core/_workflow.py index fcbdf6adbc..59ec519761 100644 --- a/src/ansys/acp/core/_workflow.py +++ b/src/ansys/acp/core/_workflow.py @@ -20,11 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from collections.abc import Callable import pathlib import shutil import tempfile import typing -from typing import Any, Callable, Optional, Protocol +from typing import Any, Protocol from . import CADGeometry, UnitSystemType from ._server.acp_instance import ACP @@ -41,7 +42,7 @@ class _LocalWorkingDir: - def __init__(self, path: Optional[PATH] = None): + def __init__(self, path: PATH | None = None): self._user_defined_working_dir = None self._temp_working_dir = None if path is None: @@ -165,7 +166,7 @@ def __init__( self, *, acp: ACP[ServerProtocol], - local_working_directory: Optional[PATH] = None, + local_working_directory: PATH | None = None, local_file_path: PATH, file_format: str, **kwargs: Any, @@ -187,7 +188,7 @@ def from_acph5_file( cls, acp: ACP[ServerProtocol], acph5_file_path: PATH, - local_working_directory: Optional[PATH] = None, + local_working_directory: PATH | None = None, ) -> "ACPWorkflow": """Instantiate an ACP Workflow from an acph5 file. @@ -215,7 +216,7 @@ def from_cdb_or_dat_file( acp: ACP[ServerProtocol], cdb_or_dat_file_path: PATH, unit_system: UnitSystemType = UnitSystemType.UNDEFINED, - local_working_directory: Optional[PATH] = None, + local_working_directory: PATH | None = None, ) -> "ACPWorkflow": """Instantiate an ACP Workflow from a cdb file. diff --git a/tests/unittests/common/linked_object_list_tester.py b/tests/unittests/common/linked_object_list_tester.py index cc08021472..bde7624a7e 100644 --- a/tests/unittests/common/linked_object_list_tester.py +++ b/tests/unittests/common/linked_object_list_tester.py @@ -22,8 +22,9 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from mypy_extensions import DefaultNamedArg, KwArg diff --git a/tests/unittests/common/tree_object_tester.py b/tests/unittests/common/tree_object_tester.py index 6ffe124907..1a0fed10eb 100644 --- a/tests/unittests/common/tree_object_tester.py +++ b/tests/unittests/common/tree_object_tester.py @@ -21,7 +21,7 @@ # SOFTWARE. from dataclasses import dataclass -from typing import Any, Optional +from typing import Any import pytest @@ -47,7 +47,7 @@ class ObjectPropertiesToTest: # If create_args exists the create method will use the create_args dictionaries # to create the object. The object # will be created for each dictionary in the list. - create_args: Optional[list[dict[str, Any]]] = None + create_args: list[dict[str, Any]] | None = None class TreeObjectTesterReadOnly: diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 599e9c16dc..445080a1e4 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -20,14 +20,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import Any, Optional, TypeVar +from typing import Any, TypeVar __all__ = ["check_property"] T = TypeVar("T") -def check_property(obj: Any, *, name: str, value: T, set_value: Optional[T] = None): +def check_property(obj: Any, *, name: str, value: T, set_value: T | None = None): assert hasattr(obj, name), f"Object '{obj}' has no property named '{name}'" assert ( getattr(obj, name) == value diff --git a/tests/unittests/test_material.py b/tests/unittests/test_material.py index a54ab375a3..e07956f0e6 100644 --- a/tests/unittests/test_material.py +++ b/tests/unittests/test_material.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import Any, Callable +from collections.abc import Callable +from typing import Any import uuid from hypothesis import HealthCheck, given, settings diff --git a/type_checks/add_methods.py b/type_checks/add_methods.py index 57820446fa..552e077547 100644 --- a/type_checks/add_methods.py +++ b/type_checks/add_methods.py @@ -1,4 +1,5 @@ -from typing import Callable, Union +from collections.abc import Callable +from typing import Union from mypy_extensions import Arg, DefaultNamedArg from typing_extensions import assert_type diff --git a/type_checks/create_methods.py b/type_checks/create_methods.py index 44531d336f..e8815a15e8 100644 --- a/type_checks/create_methods.py +++ b/type_checks/create_methods.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable from mypy_extensions import DefaultNamedArg from typing_extensions import assert_type From 35d0a6933f34ecd8ba052b1a5f4954c7a3b88734 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 17 Sep 2024 11:40:32 +0200 Subject: [PATCH 08/96] Add .git-blame-ignore-revs file --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..3e60285a43 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Dropping Python 3.9 support (pyupgrade) +d099b3b4bb80a3461f7f174a6c2944aa13b10a05 From 7e20b60f8a581ae3fcbf673dcd7d9fee2ed6ab81 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 19 Sep 2024 13:32:54 +0200 Subject: [PATCH 09/96] Implement recursive copying helper (#563) Implement a `recursive_copy` helper function, which: - takes a list of source objects - walks its children and (optionally) linked objects - copies the sub-tree to a new location, specified by a dict mapping old parent to new parent [1] Adds a dependency on `networkx`, for computing the order in which objects should be stored s.t. all their dependencies (parent, linked objects) are already stored. We may reuse the dependency graph produced for this task in other contexts. Note that the copy operation may fail if a linked object is present in the API layer, but not supported by PyACP yet. In this case, the linked object is still detected (since this is done by iterating through the protobuf object), but the object cannot be instantiated. I think this is acceptable, as it's a temporary problem and may be more desirable than silently losing the link. Other changes: - added a `.clone()` method to the edge property lists. This was used in the initial implementation of this features. Since it's consistent with the API of other objects, the method is kept. Each edge property list type separately implements `.clone()`. [2] - add a `.parent` property to tree objects, which instantiates / gets the parent via its resource path - make tree objects hashable - upload the XML coverage report separately for all unittest runs, and distinguish them (by Python version and server version) using flags. [1] more than one new parent may be needed, for example when copying a Modeling Ply (parent: Modeling Group) which links to a Fabric (parent: Model). [2] in general, the `GenericEdgePropertyType` classes have a lot of code duplication; this can be dealt with in a later PR (not urgent). --- .github/workflows/ci_cd.yml | 21 +- doc/source/api/enum_types.rst | 1 + doc/source/api/index.rst | 1 + doc/source/api/other_utils.rst | 9 + poetry.lock | 26 +- pyproject.toml | 4 +- src/ansys/acp/core/__init__.py | 3 + src/ansys/acp/core/_dependency_graph.py | 108 ++++++ src/ansys/acp/core/_recursive_copy.py | 228 +++++++++++++ src/ansys/acp/core/_server/common.py | 4 +- .../_grpc_helpers/edge_property_list.py | 9 +- .../_grpc_helpers/enum_wrapper.py | 12 +- .../_grpc_helpers/linked_object_helpers.py | 37 ++- .../_grpc_helpers/property_helper.py | 25 +- src/ansys/acp/core/_tree_objects/base.py | 29 +- .../_tree_objects/linked_selection_rule.py | 22 +- src/ansys/acp/core/_tree_objects/model.py | 2 + .../acp/core/_tree_objects/modeling_ply.py | 16 +- .../_tree_objects/oriented_selection_set.py | 6 +- src/ansys/acp/core/_tree_objects/stackup.py | 12 +- .../acp/core/_tree_objects/sublaminate.py | 12 +- .../core/_tree_objects/virtual_geometry.py | 17 +- src/ansys/acp/core/_typing_helper.py | 36 +- tests/benchmarks/conftest.py | 2 +- tests/unittests/common/tree_object_tester.py | 26 +- tests/unittests/test_analysis_ply.py | 8 +- tests/unittests/test_cad_component.py | 5 +- tests/unittests/test_edge_property_types.py | 83 +++++ tests/unittests/test_model.py | 6 + tests/unittests/test_recursive_copy.py | 308 ++++++++++++++++++ tests/unittests/test_workflow.py | 13 +- 31 files changed, 1002 insertions(+), 89 deletions(-) create mode 100644 doc/source/api/other_utils.rst create mode 100644 src/ansys/acp/core/_dependency_graph.py create mode 100644 src/ansys/acp/core/_recursive_copy.py create mode 100644 tests/unittests/test_edge_property_types.py create mode 100644 tests/unittests/test_recursive_copy.py diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index f258db0ca1..807758ea23 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -172,31 +172,32 @@ jobs: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} IMAGE_NAME: "ghcr.io/ansys/acp${{ github.event.inputs.docker_image_suffix || ':latest' }}" + - name: "Upload coverage to Codecov" + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: coverage.xml + flags: 'server-latest,python-${{ matrix.python-version }}' + - name: "Unit testing (2024R2 server)" if: matrix.python-version == env.MAIN_PYTHON_VERSION working-directory: tests/unittests run: | docker pull $IMAGE_NAME - poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html --cov-append + poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html env: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} IMAGE_NAME: "ghcr.io/ansys/acp:2024r2" - - name: "Upload coverage report (HTML)" - uses: actions/upload-artifact@v4 + - name: "Upload coverage to Codecov (2024R2 server)" if: matrix.python-version == env.MAIN_PYTHON_VERSION - with: - name: coverage-report-html - path: htmlcov - retention-days: 7 - - - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v4 - if: matrix.python-version == env.MAIN_PYTHON_VERSION env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: coverage.xml + flags: 'server-242,python-${{ matrix.python-version }}' - name: Benchmarks working-directory: tests/benchmarks diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index e609b287cc..1ba5f80310 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -21,6 +21,7 @@ Enumeration data types FeFormat GeometricalRuleType IgnorableEntity + LinkedObjectHandling LookUpTable3DInterpolationAlgorithm LookUpTableColumnValueType NodalDataType diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index b02f968c6d..f4cab8f7a6 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -16,6 +16,7 @@ and attributes. material_property_sets enum_types plot_utils + other_utils workflow example_helpers internal diff --git a/doc/source/api/other_utils.rst b/doc/source/api/other_utils.rst new file mode 100644 index 0000000000..5a16ddd96b --- /dev/null +++ b/doc/source/api/other_utils.rst @@ -0,0 +1,9 @@ +Other utilities +--------------- + +.. currentmodule:: ansys.acp.core + +.. autosummary:: + :toctree: _autosummary + + recursive_copy diff --git a/poetry.lock b/poetry.lock index d9a07ca013..e85f831867 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2107,13 +2107,13 @@ referencing = ">=0.31.0" [[package]] name = "jupyter-client" -version = "8.6.2" +version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, - {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, ] [package.dependencies] @@ -2901,6 +2901,24 @@ files = [ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] +[[package]] +name = "networkx" +version = "3.3" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +files = [ + {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, + {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, +] + +[package.extras] +default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + [[package]] name = "nodeenv" version = "1.9.1" @@ -5002,4 +5020,4 @@ examples = ["ansys-dpf-composites", "ansys-mapdl-core", "ansys-mechanical-core", [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "18c93570d47795eefec3cb312ff1cb5d1d89b55d4c1e5d84599dccd6d23d53d2" +content-hash = "b387ab7ac9a40f004afc5eb3571f76df4294625c4b70c3b0e77e32f2d8293c44" diff --git a/pyproject.toml b/pyproject.toml index 28ee3abea8..22a6327e03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ ansys-tools-path = ">=0" ansys-tools-local-product-launcher = ">=0.1" ansys-tools-filetransfer = ">=0.1" pyvista = ">=0.42.0" +networkx = ">=3.0.0" # Dependencies for the examples. Update also the 'dev' group when # these are updated. @@ -147,8 +148,9 @@ pretty = true [[tool.mypy.overrides]] module = [ "docker.*", - "grpc.*", "grpc_health.*", + "grpc.*", + "networkx", "scipy.optimize", "ansys.mapdl", "ansys.mapdl.core", diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 1a7291dbf4..c254523b1b 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -30,6 +30,7 @@ from . import example_helpers, material_property_sets from ._model_printer import get_model_tree, print_model from ._plotter import get_directions_plotter +from ._recursive_copy import LinkedObjectHandling, recursive_copy from ._server import ( ACP, ConnectLaunchConfig, @@ -191,6 +192,7 @@ "Lamina", "launch_acp", "LaunchMode", + "LinkedObjectHandling", "LinkedSelectionRule", "LookUpTable1D", "LookUpTable1DColumn", @@ -223,6 +225,7 @@ "ProductionPly", "ProductionPlyElementalData", "ProductionPlyNodalData", + "recursive_copy", "Rosette", "RosetteSelectionMethod", "RosetteType", diff --git a/src/ansys/acp/core/_dependency_graph.py b/src/ansys/acp/core/_dependency_graph.py new file mode 100644 index 0000000000..87f2c259df --- /dev/null +++ b/src/ansys/acp/core/_dependency_graph.py @@ -0,0 +1,108 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from collections.abc import Iterable, Iterator +from dataclasses import dataclass + +import networkx as nx + +from ._tree_objects._grpc_helpers.linked_object_helpers import get_linked_paths +from ._tree_objects._grpc_helpers.mapping import Mapping +from ._tree_objects._grpc_helpers.polymorphic_from_pb import tree_object_from_resource_path +from ._tree_objects.base import CreatableTreeObject, TreeObject + + +@dataclass +class _WalkTreeOptions: + include_children: bool + include_linked_objects: bool + + +def _build_dependency_graph( + *, source_objects: Iterable[CreatableTreeObject], options: _WalkTreeOptions +) -> nx.DiGraph: + """Build a dependency graph of the given objects.""" + graph = nx.DiGraph() + + # We need to manually keep track of which objects have been visited, + # since the node may also be created when being linked to. + visited_objects: set[CreatableTreeObject] = set() + for tree_object in source_objects: + _build_dependency_graph_impl( + tree_object=tree_object, graph=graph, visited_objects=visited_objects, options=options + ) + return graph + + +def _build_dependency_graph_impl( + *, + tree_object: CreatableTreeObject, + graph: nx.DiGraph, + visited_objects: set[CreatableTreeObject], + options: _WalkTreeOptions, +) -> None: + + if tree_object in visited_objects: + return + + visited_objects.add(tree_object) + graph.add_node(tree_object) + + if options.include_children: + for child_object in _yield_child_objects(tree_object): + if not isinstance(child_object, CreatableTreeObject): + continue + graph.add_edge(child_object, tree_object) + _build_dependency_graph_impl( + tree_object=child_object, + graph=graph, + visited_objects=visited_objects, + options=options, + ) + if options.include_linked_objects: + for linked_object in _yield_linked_objects(tree_object): + graph.add_edge(tree_object, linked_object) + _build_dependency_graph_impl( + tree_object=linked_object, + graph=graph, + visited_objects=visited_objects, + options=options, + ) + + +def _yield_child_objects(tree_object: TreeObject) -> Iterator[TreeObject]: + for attr_name in tree_object._GRPC_PROPERTIES: + try: + attr = getattr(tree_object, attr_name) + except (AttributeError, RuntimeError): + continue + if isinstance(attr, Mapping): + yield from attr.values() + + +def _yield_linked_objects(tree_object: TreeObject) -> Iterator[CreatableTreeObject]: + for linked_path in get_linked_paths(tree_object._pb_object.properties): + linked_object = tree_object_from_resource_path( + linked_path, server_wrapper=tree_object._server_wrapper + ) + assert isinstance(linked_object, CreatableTreeObject) + yield linked_object diff --git a/src/ansys/acp/core/_recursive_copy.py b/src/ansys/acp/core/_recursive_copy.py new file mode 100644 index 0000000000..ce9ce2a11b --- /dev/null +++ b/src/ansys/acp/core/_recursive_copy.py @@ -0,0 +1,228 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import collections +from collections.abc import Iterable + +import networkx as nx + +from ._dependency_graph import _build_dependency_graph, _WalkTreeOptions +from ._tree_objects import LookUpTable1D, LookUpTable1DColumn, LookUpTable3D, LookUpTable3DColumn +from ._tree_objects._grpc_helpers.linked_object_helpers import get_linked_paths +from ._tree_objects.base import CreatableTreeObject, TreeObject +from ._typing_helper import StrEnum +from ._utils.resource_paths import common_path, to_parts + +__all__ = ["recursive_copy", "LinkedObjectHandling"] + + +class LinkedObjectHandling(StrEnum): + """Defines options for handling linked objects when copying a tree of ACP objects.""" + + KEEP = "keep" + COPY = "copy" + DISCARD = "discard" + + +def recursive_copy( + *, + source_objects: Iterable[CreatableTreeObject], + parent_mapping: dict[TreeObject, TreeObject], + linked_object_handling: LinkedObjectHandling | str = "keep", +) -> dict[CreatableTreeObject, CreatableTreeObject]: + """Recursively copy a tree of ACP objects. + + This function copies a tree of ACP objects, starting from the given source objects. + The copied tree includes all child objects. Linked objects (such as a Fabric linked to + by a Modeling Ply) can optionally be included, controlled by the + ``linked_object_handling`` argument. + + To specify where the new objects should be stored, you must provide a dictionary + in the ``parent_mapping`` argument. The keys of the dictionary are the original + parent objects, and the values are the new parent objects. + Note that this mapping may need to contain parent objects that are not direct parents + of the source objects, if another branch of the tree is included via linked objects. + + The ``parent_mapping`` argument can also include objects which are part of the + ``source_objects`` list. In this case, the function will not create a new object for + the parent, but will use the existing object instead. + + The function returns a ``dict`` mapping the original objects to the newly created + objects. + + .. note:: + + Only attributes supported by PyACP are copied to the new objects. + + Parameters + ---------- + source_objects : + The starting point of the tree to copy. + parent_mapping : + A list of tuples defining where the new objects are stored. Each tuple contains + the original parent object as the first element and the new parent object as the + second element. + linked_object_handling : + Defines how linked objects are handled. An example of a linked object is a Fabric + linked to by a Modeling Ply. + + The following options are available: + + - ``"keep"``: Keep linking to the original objects, and do not + copy them (unless they are otherwise included in the tree). + - ``"copy"``: Copy the linked objects, and replace the links. + - ``"discard"``: Discard object links. + + Note that when copying objects between two models, only the ``"copy"`` and + ``"discard"`` options are valid. If you wish to use links to existing objects, + the ``"copy"`` option can be used, specifying how links should be replaced in + the ``parent_mapping`` argument. + + Returns + ------- + : + A mapping of the newly created objects. The keys are the original objects, + and the values are the new objects. + + Examples + -------- + To copy all Modeling Groups and associated definitions from one model to another, + you can use the following code: + + .. code-block:: python + + import ansys.acp.core as pyacp + + model1 = ... # loaded in some way + model2 = ... # loaded in some way + + pyacp.recursive_copy( + source_objects=model1.modeling_groups.values(), + parent_mapping={model1: model2}, + linked_object_handling="copy", + ) + + To copy all definitions from one model to another, you can use the following code: + + .. code-block:: python + + import ansys.acp.core as pyacp + + model1 = ... # loaded in some way + model2 = ... # loaded in some way + + pyacp.recursive_copy( + source_objects=[model1], + parent_mapping={model1: model2}, + linked_object_handling="copy", + ) + """ + # Check that the given source objects and parent mapping keys belong to the same + # model. + common_source_path = common_path( + *[obj._resource_path.value for obj in list(source_objects) + list(parent_mapping.keys())] + ) + if len(to_parts(common_source_path)) < 2: + raise ValueError( + "The 'source_objects' and 'parent_mapping' keys must all belong to the same model." + ) + common_target_path = common_path(*[obj._resource_path.value for obj in parent_mapping.values()]) + if len(to_parts(common_target_path)) < 2: + raise ValueError("The 'parent_mapping' values must all belong to the same model.") + if linked_object_handling == LinkedObjectHandling.KEEP: + if len(to_parts(common_path(common_source_path, common_target_path))) < 2: + raise ValueError( + "When using 'linked_object_handling=\"keep\"', objects cannot be copied from one model " + "to another. The objects in 'source_objects' and 'parent_mapping' must all belong to the " + 'same model. Use \'linked_object_handling="copy"\' or linked_object_handling="discard"\' ' + "to copy objects between models." + ) + + linked_object_handling = LinkedObjectHandling(linked_object_handling) + + options = _WalkTreeOptions( + include_children=True, + include_linked_objects=linked_object_handling == LinkedObjectHandling.COPY, + ) + # Build up a graph of the objects to clone. Graph edges represent a dependency: + # - from child to parent node + # - from source to target of a link + graph = _build_dependency_graph(source_objects=source_objects, options=options) + + new_object_mapping: dict[CreatableTreeObject, CreatableTreeObject] = {} + replacement_mapping = collections.ChainMap[TreeObject, TreeObject]( + new_object_mapping, parent_mapping # type: ignore + ) + + # keep track of the new resource paths for easy replacement of linked objects + resource_path_replacement_mapping = { + obj._resource_path.value: new_obj._resource_path.value + for obj, new_obj in parent_mapping.items() + } + + # The 'topological_sort' of the graph ensures that each node is only handled + # once its parent and linked objects are stored. + for tree_object in reversed(list(nx.topological_sort(graph))): + if tree_object in replacement_mapping: + # Skip nodes which are already copied (e.g. coming from the parent_mapping) + continue + + if isinstance(tree_object, (LookUpTable1DColumn, LookUpTable3DColumn)): + # handled explicitly while copying the LookUpTable object + if tree_object.name == "Location": + continue + + new_tree_object = tree_object.clone( + unlink=linked_object_handling == LinkedObjectHandling.DISCARD + ) + + # If the linked objects are also copied, replace them with the new objects. + # Otherwise, we can directly store the new object. + if linked_object_handling == LinkedObjectHandling.COPY: + for linked_resource_path in get_linked_paths(new_tree_object._pb_object.properties): + linked_resource_path.value = resource_path_replacement_mapping[ + linked_resource_path.value + ] + + try: + new_parent = replacement_mapping[tree_object.parent] + except KeyError as exc: + raise KeyError( + f"Parent object not found in 'parent_mapping' for object '{tree_object!r}'." + ) from exc + + new_tree_object.store(parent=new_parent) + + # NOTE: if there are more type-specific fixes needed, we may want + # to implement a more generic way to handle these. + # Explicit fix for LookUpTable, since the Location column needs to + # be set correctly s.t. other columns may be stored. + if isinstance(new_tree_object, (LookUpTable1D, LookUpTable3D)): + assert isinstance(tree_object, (LookUpTable1D, LookUpTable3D)) + new_tree_object.columns["Location"].data = tree_object.columns["Location"].data + + new_object_mapping[tree_object] = new_tree_object + resource_path_replacement_mapping[tree_object._resource_path.value] = ( + new_tree_object._resource_path.value + ) + + return new_object_mapping diff --git a/src/ansys/acp/core/_server/common.py b/src/ansys/acp/core/_server/common.py index a8e93615c0..d4e177b6cf 100644 --- a/src/ansys/acp/core/_server/common.py +++ b/src/ansys/acp/core/_server/common.py @@ -31,12 +31,12 @@ __all__ = ["LaunchMode"] -class ServerKey(StrEnum): # type: ignore +class ServerKey(StrEnum): MAIN = "main" FILE_TRANSFER = "file_transfer" -class LaunchMode(StrEnum): # type: ignore +class LaunchMode(StrEnum): """Available launch modes for ACP.""" DIRECT = "direct" diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py index 3f51c5c438..6e523ea66a 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py @@ -33,6 +33,7 @@ from .._object_cache import ObjectCacheMixin, constructor_with_cache from ..base import CreatableTreeObject from .property_helper import _exposed_grpc_property, _wrap_doc, grpc_data_getter, grpc_data_setter +from .protocols import GrpcObjectBase __all__ = [ "EdgePropertyList", @@ -42,7 +43,7 @@ ] -class GenericEdgePropertyType(Protocol): +class GenericEdgePropertyType(GrpcObjectBase, Protocol): """Protocol for the definition of ACP edge properties such as FabricWithAngle.""" def __init__(self, *kwargs: Any) -> None: ... @@ -61,6 +62,10 @@ def _check(self) -> bool: ... def _set_callback_apply_changes(self, callback_apply_changes: Callable[[], None]) -> None: ... + def clone(self) -> Self: + """Create a new unstored object with the same properties.""" + raise NotImplementedError + ValueT = TypeVar("ValueT", bound=GenericEdgePropertyType) @@ -170,7 +175,7 @@ def _object_list(self) -> list[ValueT]: # There are two scenarios when the parent object becomes # stored: # - The _object_list already contained some values. In this case, we - # simply keep it, and make a (inexaustive) check that the size + # simply keep it, and make a (inexhaustive) check that the size # matches. # - The parent object was default-constructed and then its _pb_object # was then replaced (e.g. by a call to _from_object_info). In this diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py index eeb9d6ea86..3884d7f80b 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py @@ -27,9 +27,11 @@ from ansys.acp.core._typing_helper import StrEnum - # mypy doesn't understand this dynamically created Enum, so we have to # fall back to 'Any'. +_StrEnumT = Any + + def wrap_to_string_enum( class_name: str, proto_enum: Any, @@ -39,7 +41,7 @@ def wrap_to_string_enum( value_converter: Callable[[str], str] = lambda val: val.lower(), doc: str, explicit_value_list: tuple[int, ...] | None = None, -) -> tuple[StrEnum, Callable[[StrEnum], int], Callable[[int], StrEnum]]: +) -> tuple[_StrEnumT, Callable[[_StrEnumT], int], Callable[[int], _StrEnumT]]: """Create a string Enum with the same keys as the given protobuf Enum. Values of the enum are the keys, converted to lowercase. @@ -65,13 +67,13 @@ def wrap_to_string_enum( to_pb_conversion_dict[enum_value] = pb_value from_pb_conversion_dict[pb_value] = enum_value - res_enum = StrEnum(class_name, fields, module=module) + res_enum: _StrEnumT = StrEnum(class_name, fields, module=module) # type: ignore res_enum.__doc__ = doc - def to_pb_conversion_func(val: StrEnum) -> int: + def to_pb_conversion_func(val: _StrEnumT) -> int: return to_pb_conversion_dict[val] - def from_pb_conversion_func(val: int) -> StrEnum: + def from_pb_conversion_func(val: int) -> _StrEnumT: return res_enum(from_pb_conversion_dict[val]) return ( diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py index fd8b5d86d1..1110ece089 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_helpers.py @@ -25,28 +25,41 @@ from google.protobuf.descriptor import FieldDescriptor from google.protobuf.message import Message -from ansys.api.acp.v0.base_pb2 import CollectionPath, ResourcePath +from ansys.api.acp.v0.base_pb2 import ResourcePath -__all__ = ("unlink_objects", "linked_path_fields") +__all__ = ("unlink_objects", "get_linked_paths") def unlink_objects(pb_object: Message) -> None: """Remove all ResourcePaths and CollectionPaths from a protobuf object.""" - for parent_message, field_descriptor, _ in linked_path_fields(pb_object): + for parent_message, field_descriptor, _ in _linked_path_fields(pb_object): parent_message.ClearField(field_descriptor.name) -def linked_path_fields( +def get_linked_paths(pb_object: Message) -> Iterable[ResourcePath]: + """Get all resource paths present in a protobuf object.""" + for _, field_descriptor, field_value in _linked_path_fields(pb_object): + if field_descriptor.label == field_descriptor.LABEL_REPEATED: + yield from field_value # type: ignore + else: + yield field_value # type: ignore + + +def _linked_path_fields( pb_object: Message, -) -> Iterable[tuple[Message, FieldDescriptor, ResourcePath | CollectionPath]]: - """Get all linked paths from a protobuf object. +) -> Iterable[tuple[Message, FieldDescriptor, Message]]: + """Get the field field information for resource paths in the message. - Get tuples (parent_message, field_descriptor, {resource_path or collection_path}) - describing all resource or collection paths present in the protobuf - object. + Get tuples (parent_message, field_descriptor, field_value) describing + all resource paths present in the protobuf object. Note that the fields + can also be repeated (containing multiple resource paths). """ for field_descriptor, field_value in pb_object.ListFields(): - if isinstance(field_value, (ResourcePath, CollectionPath)): + if getattr(field_descriptor.message_type, "name", None) == "ResourcePath": yield (pb_object, field_descriptor, field_value) - elif isinstance(field_value, Message): - yield from linked_path_fields(field_value) + elif field_descriptor.type == field_descriptor.TYPE_MESSAGE: + if field_descriptor.label == field_descriptor.LABEL_REPEATED: + for sub_obj in field_value: + yield from _linked_path_fields(sub_obj) + else: + yield from _linked_path_fields(field_value) diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py index c6300c812f..50bac3c7e4 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py @@ -29,7 +29,7 @@ from collections.abc import Callable from functools import reduce -from typing import Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from google.protobuf.message import Message @@ -51,14 +51,21 @@ _FROM_PROTOBUF_T = Callable[[_PROTOBUF_T], _GET_T] -class _exposed_grpc_property(property): - """Mark a property as exposed via gRPC. +if TYPE_CHECKING: # pragma: no cover + # This is needed because mypy does not understand custom property + # subclasses. + # See https://github.com/python/mypy/issues/6158 + _exposed_grpc_property = property +else: - Wrapper around 'property', used to signal that the object should - be collected into the '_GRPC_PROPERTIES' class attribute. - """ + class _exposed_grpc_property(property): + """Mark a property as exposed via gRPC. + + Wrapper around 'property', used to signal that the object should + be collected into the '_GRPC_PROPERTIES' class attribute. + """ - pass + pass T = TypeVar("T", bound=type[GrpcObjectBase]) @@ -90,10 +97,8 @@ def grpc_linked_object_getter(name: str) -> Callable[[Readable], Any]: """Create a getter method which obtains the linked server object.""" def inner(self: Readable) -> CreatableFromResourcePath | None: - # Import here to avoid circular references. Cannot use the registry before - # all the object have been imported. if not self._is_stored: - raise Exception("Cannot get linked object from unstored object") + raise RuntimeError(f"Cannot get linked object '{name}' from unstored object") self._get() object_resource_path = _get_data_attribute(self._pb_object, name) diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index 070c598857..758f993719 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -42,8 +42,11 @@ from .._utils.resource_paths import join as _rp_join from .._utils.resource_paths import to_parts from ._grpc_helpers.exceptions import wrap_grpc_errors -from ._grpc_helpers.linked_object_helpers import linked_path_fields, unlink_objects -from ._grpc_helpers.polymorphic_from_pb import CreatableFromResourcePath +from ._grpc_helpers.linked_object_helpers import get_linked_paths, unlink_objects +from ._grpc_helpers.polymorphic_from_pb import ( + CreatableFromResourcePath, + tree_object_from_resource_path, +) from ._grpc_helpers.property_helper import ( _get_data_attribute, grpc_data_property, @@ -100,6 +103,9 @@ def __eq__(self: Self, other: Any) -> bool: return self is other return self._resource_path.value == other._resource_path.value + def __hash__(self) -> int: + return id(self) + @classmethod @constructor_with_cache( key_getter=lambda object_info, *args, **kwargs: object_info.info.resource_path.value, @@ -145,6 +151,23 @@ def _server_wrapper(self) -> ServerWrapper: def _is_stored(self) -> bool: return self._server_wrapper_store is not None + @property + def parent(self) -> CreatableFromResourcePath: + """The parent of the object.""" + if not self._is_stored: + raise RuntimeError("Cannot get the parent of an unstored object.") + rp_parts = to_parts(self._resource_path.value) + if len(rp_parts) < 3: + raise RuntimeError("The object does not have a parent.") + + parent_path = _rp_join(*rp_parts[:-2]) + parent = tree_object_from_resource_path( + ResourcePath(value=parent_path), server_wrapper=self._server_wrapper + ) + if parent is None: + raise RuntimeError("The parent object could not be found.") + return parent + def __repr__(self) -> str: return f"<{type(self).__name__} with name '{self.name}'>" @@ -307,7 +330,7 @@ def store(self: Self, parent: TreeObject) -> None: # check that all linked objects are located in the same model path_values = [collection_path.value] + [ - path.value for _, _, path in linked_path_fields(self._pb_object.properties) + path.value for path in get_linked_paths(self._pb_object.properties) ] # filter out empty paths path_values = [path for path in path_values if path] diff --git a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py index 34a67a731f..6dd88e71a4 100644 --- a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py @@ -32,6 +32,7 @@ from ._grpc_helpers.edge_property_list import GenericEdgePropertyType from ._grpc_helpers.polymorphic_from_pb import tree_object_from_resource_path +from ._grpc_helpers.property_helper import _exposed_grpc_property, mark_grpc_properties from .base import CreatableTreeObject from .cutoff_selection_rule import CutoffSelectionRule from .cylindrical_selection_rule import CylindricalSelectionRule @@ -63,6 +64,7 @@ ] +@mark_grpc_properties class LinkedSelectionRule(GenericEdgePropertyType): r"""Defines selection rules linked to a Boolean Selection Rule or Modeling Ply. @@ -123,7 +125,7 @@ def __init__( self.parameter_1 = parameter_1 self.parameter_2 = parameter_2 - @property + @_exposed_grpc_property def selection_rule(self) -> _LINKABLE_SELECTION_RULE_TYPES: """Link to an existing selection rule.""" return self._selection_rule @@ -134,7 +136,7 @@ def selection_rule(self, value: _LINKABLE_SELECTION_RULE_TYPES) -> None: if self._callback_apply_changes is not None: self._callback_apply_changes() - @property + @_exposed_grpc_property def operation_type(self) -> BooleanOperationType: """Operation to combine the selection rule with other selection rules.""" return self._operation_type @@ -154,7 +156,7 @@ def operation_type(self, value: BooleanOperationType) -> None: if self._callback_apply_changes is not None: self._callback_apply_changes() - @property + @_exposed_grpc_property def template_rule(self) -> bool: """Whether the selection rule is a template rule.""" return self._template_rule @@ -165,7 +167,7 @@ def template_rule(self, value: bool) -> None: if self._callback_apply_changes is not None: self._callback_apply_changes() - @property + @_exposed_grpc_property def parameter_1(self) -> float: """First template parameter of the selection rule.""" return self._parameter_1 @@ -176,7 +178,7 @@ def parameter_1(self, value: float) -> None: if self._callback_apply_changes is not None: self._callback_apply_changes() - @property + @_exposed_grpc_property def parameter_2(self) -> float: """Second template parameter of the selection rule.""" return self._parameter_2 @@ -266,3 +268,13 @@ def __repr__(self) -> str: f"parameter_1={self.parameter_1}, " f"parameter_2={self.parameter_2})" ) + + def clone(self) -> LinkedSelectionRule: + """Create a new unstored LinkedSelectionRule with the same properties.""" + return LinkedSelectionRule( + selection_rule=self.selection_rule, + operation_type=self.operation_type, + template_rule=self.template_rule, + parameter_1=self.parameter_1, + parameter_2=self.parameter_2, + ) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 421b58cd62..b4fec9c781 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -112,6 +112,7 @@ from .material import Material from .modeling_group import ModelingGroup from .modeling_ply import ModelingPly +from .object_registry import register from .oriented_selection_set import OrientedSelectionSet from .parallel_selection_rule import ParallelSelectionRule from .rosette import Rosette @@ -197,6 +198,7 @@ class ModelNodalData(NodalData): @mark_grpc_properties +@register class Model(TreeObject): """Defines an ACP Model. diff --git a/src/ansys/acp/core/_tree_objects/modeling_ply.py b/src/ansys/acp/core/_tree_objects/modeling_ply.py index 6ad2dd5140..5b2b201d69 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/modeling_ply.py @@ -41,6 +41,7 @@ from ._grpc_helpers.linked_object_list import define_linked_object_list from ._grpc_helpers.mapping import get_read_only_collection_property from ._grpc_helpers.property_helper import ( + _exposed_grpc_property, grpc_data_property, grpc_data_property_read_only, grpc_link_property, @@ -114,6 +115,7 @@ class ModelingPlyNodalData(NodalData): ply_offset: VectorData | None = None +@mark_grpc_properties class TaperEdge(GenericEdgePropertyType): """Defines a taper edge. @@ -135,7 +137,7 @@ def __init__(self, edge_set: EdgeSet, *, angle: float, offset: float): self.angle = angle self.offset = offset - @property + @_exposed_grpc_property def edge_set(self) -> EdgeSet: """Edge along which the ply tapering is applied.""" return self._edge_set @@ -148,7 +150,7 @@ def edge_set(self, edge_set: EdgeSet) -> None: if self._callback_apply_changes is not None: self._callback_apply_changes() - @property + @_exposed_grpc_property def angle(self) -> float: """Angle between the cutting plane and the reference surface.""" return self._angle @@ -159,7 +161,7 @@ def angle(self, angle: float) -> None: if self._callback_apply_changes is not None: self._callback_apply_changes() - @property + @_exposed_grpc_property def offset(self) -> float: """Move the cutting plane along the out-of-plane direction. @@ -221,6 +223,14 @@ def __repr__(self) -> str: f"angle={self.angle!r}, offset={self.offset!r})" ) + def clone(self) -> Self: + """Create a new unstored TaperEdge with the same properties.""" + return type(self)( + edge_set=self.edge_set, + angle=self.angle, + offset=self.offset, + ) + @mark_grpc_properties @register diff --git a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py index f3b52fbcf6..613e60c355 100644 --- a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py +++ b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py @@ -60,6 +60,7 @@ rosette_selection_method_to_pb, status_type_from_pb, ) +from .lookup_table_1d_column import LookUpTable1DColumn from .lookup_table_3d_column import LookUpTable3DColumn from .object_registry import register from .parallel_selection_rule import ParallelSelectionRule @@ -179,7 +180,7 @@ def __init__( draping_material_model: DrapingMaterialType = DrapingMaterialType.WOVEN, draping_ud_coefficient: float = 0.0, rotation_angle: float = 0.0, - reference_direction_field: LookUpTable3DColumn | None = None, + reference_direction_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, ): super().__init__(name=name) self.element_sets = element_sets @@ -270,7 +271,8 @@ def _create_stub(self) -> oriented_selection_set_pb2_grpc.ObjectServiceStub: ) reference_direction_field = grpc_link_property( - "properties.reference_direction_field", allowed_types=LookUpTable3DColumn + "properties.reference_direction_field", + allowed_types=(LookUpTable3DColumn, LookUpTable1DColumn), ) elemental_data = elemental_data_property(OrientedSelectionSetElementalData) diff --git a/src/ansys/acp/core/_tree_objects/stackup.py b/src/ansys/acp/core/_tree_objects/stackup.py index a9a2936453..f12ff5d762 100644 --- a/src/ansys/acp/core/_tree_objects/stackup.py +++ b/src/ansys/acp/core/_tree_objects/stackup.py @@ -25,6 +25,8 @@ from collections.abc import Callable, Iterable, Sequence from typing import Any +from typing_extensions import Self + from ansys.api.acp.v0 import stackup_pb2, stackup_pb2_grpc from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty @@ -34,6 +36,7 @@ define_edge_property_list, ) from ._grpc_helpers.property_helper import ( + _exposed_grpc_property, grpc_data_property, grpc_data_property_read_only, grpc_link_property, @@ -62,6 +65,7 @@ __all__ = ["Stackup", "FabricWithAngle"] +@mark_grpc_properties class FabricWithAngle(GenericEdgePropertyType): """Defines a fabric of a stackup. @@ -79,7 +83,7 @@ def __init__(self, fabric: Fabric, angle: float = 0.0): self.fabric = fabric self.angle = angle - @property + @_exposed_grpc_property def fabric(self) -> Fabric: """Linked fabric.""" return self._fabric @@ -92,7 +96,7 @@ def fabric(self, value: Fabric) -> None: if self._callback_apply_changes: self._callback_apply_changes() - @property + @_exposed_grpc_property def angle(self) -> float: """Orientation angle in degree of the fabric with respect to the reference direction.""" return self._angle @@ -139,6 +143,10 @@ def __eq__(self, other: Any) -> bool: def __repr__(self) -> str: return f"FabricWithAngle(fabric={self.fabric.__repr__()}, angle={self.angle})" + def clone(self) -> Self: + """Create a new unstored FabricWithAngle with the same properties.""" + return type(self)(fabric=self.fabric, angle=self.angle) + @mark_grpc_properties @register diff --git a/src/ansys/acp/core/_tree_objects/sublaminate.py b/src/ansys/acp/core/_tree_objects/sublaminate.py index d2877f9750..f3e7f03ad1 100644 --- a/src/ansys/acp/core/_tree_objects/sublaminate.py +++ b/src/ansys/acp/core/_tree_objects/sublaminate.py @@ -26,6 +26,8 @@ import typing from typing import Any, Union, get_args +from typing_extensions import Self + from ansys.api.acp.v0 import sublaminate_pb2, sublaminate_pb2_grpc from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty @@ -36,6 +38,7 @@ ) from ._grpc_helpers.polymorphic_from_pb import tree_object_from_resource_path from ._grpc_helpers.property_helper import ( + _exposed_grpc_property, grpc_data_property, grpc_data_property_read_only, mark_grpc_properties, @@ -51,6 +54,7 @@ _LINKABLE_MATERIAL_TYPES = Union[Fabric, Stackup] +@mark_grpc_properties class Lamina(GenericEdgePropertyType): """ Class to link a material with a sub-laminate. @@ -69,7 +73,7 @@ def __init__(self, material: _LINKABLE_MATERIAL_TYPES, angle: float = 0.0): self.material = material self.angle = angle - @property + @_exposed_grpc_property def material(self) -> _LINKABLE_MATERIAL_TYPES: """Link to an existing fabric or stackup.""" return self._material @@ -85,7 +89,7 @@ def material(self, value: _LINKABLE_MATERIAL_TYPES) -> None: if self._callback_apply_changes: self._callback_apply_changes() - @property + @_exposed_grpc_property def angle(self) -> float: """Orientation angle in degree of the material with respect to the reference direction.""" return self._angle @@ -143,6 +147,10 @@ def __eq__(self, other: Any) -> bool: def __repr__(self) -> str: return f"Lamina(material={self.material.__repr__()}, angle={self.angle})" + def clone(self) -> Self: + """Create a new unstored Lamina with the same properties.""" + return type(self)(material=self.material, angle=self.angle) + @mark_grpc_properties @register diff --git a/src/ansys/acp/core/_tree_objects/virtual_geometry.py b/src/ansys/acp/core/_tree_objects/virtual_geometry.py index 3ea22b7ee4..0af3696054 100644 --- a/src/ansys/acp/core/_tree_objects/virtual_geometry.py +++ b/src/ansys/acp/core/_tree_objects/virtual_geometry.py @@ -26,6 +26,8 @@ import typing from typing import Any +from typing_extensions import Self + from ansys.api.acp.v0 import base_pb2, virtual_geometry_pb2, virtual_geometry_pb2_grpc from ._grpc_helpers.edge_property_list import ( @@ -33,7 +35,11 @@ define_add_method, define_edge_property_list, ) -from ._grpc_helpers.property_helper import grpc_data_property_read_only, mark_grpc_properties +from ._grpc_helpers.property_helper import ( + _exposed_grpc_property, + grpc_data_property_read_only, + mark_grpc_properties, +) from .base import CreatableTreeObject, IdTreeObject from .cad_geometry import CADGeometry from .enums import status_type_from_pb, virtual_geometry_dimension_from_pb @@ -43,6 +49,7 @@ from .cad_component import CADComponent +@mark_grpc_properties class SubShape(GenericEdgePropertyType): """Represents a sub-shape of a virtual geometry.""" @@ -51,7 +58,7 @@ def __init__(self, cad_geometry: CADGeometry, path: str): self.cad_geometry = cad_geometry self.path = path - @property + @_exposed_grpc_property def cad_geometry(self) -> CADGeometry: """Linked CAD geometry.""" return self._cad_geometry @@ -64,7 +71,7 @@ def cad_geometry(self, value: CADGeometry) -> None: if self._callback_apply_changes: self._callback_apply_changes() - @property + @_exposed_grpc_property def path(self) -> str: """Topological path of the sub-shape within the CAD geometry.""" return self._path @@ -115,6 +122,10 @@ def __eq__(self, other: Any) -> bool: def __repr__(self) -> str: return f"SubShape(cad_geometry={self._cad_geometry.__repr__()}, path={self._path})" + def clone(self) -> Self: + """Create a new unstored SubShape with the same properties.""" + return type(self)(cad_geometry=self.cad_geometry, path=self.path) + @mark_grpc_properties @register diff --git a/src/ansys/acp/core/_typing_helper.py b/src/ansys/acp/core/_typing_helper.py index 10f301aa63..385be37b01 100644 --- a/src/ansys/acp/core/_typing_helper.py +++ b/src/ansys/acp/core/_typing_helper.py @@ -21,24 +21,34 @@ # SOFTWARE. """Helpers for defining type annotations.""" +import enum import os -from typing import Union +from typing import TYPE_CHECKING, Union __all__ = ["PATH", "StrEnum"] PATH = Union[str, os.PathLike[str]] -try: - from enum import StrEnum # type: ignore -except ImportError: - # For Python 3.10 and below, emulate the behavior of StrEnum by - # inheriting from str and enum.Enum. - # Note that this does *not* work on Python 3.11+, since the default - # Enum format method has changed and will not return the value of - # the enum member. - import enum - - class StrEnum(str, enum.Enum): # type: ignore +# For Python 3.10 and below, emulate the behavior of StrEnum by +# inheriting from str and enum.Enum. +# Note that this does *not* work on Python 3.11+, since the default +# Enum format method has changed and will not return the value of +# the enum member. +# When type checking, always use the Python 3.10 workaround, otherwise +# the StrEnum resolves as 'Any'. +if TYPE_CHECKING: + + class StrEnum(str, enum.Enum): """String enum.""" - pass +else: + try: + from enum import StrEnum + except ImportError: + + import enum + + class StrEnum(str, enum.Enum): + """String enum.""" + + pass diff --git a/tests/benchmarks/conftest.py b/tests/benchmarks/conftest.py index 8bf8445546..fd7e7da0fb 100644 --- a/tests/benchmarks/conftest.py +++ b/tests/benchmarks/conftest.py @@ -129,7 +129,7 @@ def launch_benchmark_server(network_options): "PYACP_DELAY": f"{network_options.delay_ms}ms", "PYACP_RATE": f"{network_options.rate_kbit}kbit", } - acp = pyacp.launch_acp(config=conf, launch_mode=pyacp.LaunchMode.DOCKER_COMPOSE) # type: ignore + acp = pyacp.launch_acp(config=conf, launch_mode=pyacp.LaunchMode.DOCKER_COMPOSE) acp.wait(SERVER_STARTUP_TIMEOUT) return acp diff --git a/tests/unittests/common/tree_object_tester.py b/tests/unittests/common/tree_object_tester.py index 1a0fed10eb..f3640cd68c 100644 --- a/tests/unittests/common/tree_object_tester.py +++ b/tests/unittests/common/tree_object_tester.py @@ -111,6 +111,14 @@ def test_collection_getitem_inexistent(collection_test_data): object_collection[INEXISTENT_ID] assert object_collection.get(INEXISTENT_ID) is None + @staticmethod + def test_parent_access(collection_test_data, parent_object): + """Test the parent access of the objects in the collection.""" + object_collection, _, object_ids = collection_test_data + ref_id = object_ids[0] + + assert object_collection[ref_id].parent is parent_object + class TreeObjectTester(TreeObjectTesterReadOnly): COLLECTION_NAME: str @@ -183,10 +191,11 @@ def test_properties(tree_object, object_properties: ObjectPropertiesToTest): with pytest.raises(AttributeError): setattr(tree_object, prop, value) + string_representation = str(tree_object) for prop, _ in object_properties.read_only + object_properties.read_write: - assert f"{prop}=" in str( - tree_object - ), f"{prop} not found in object string: {str(tree_object)}" + assert ( + f"{prop}=" in string_representation + ), f"{prop} not found in object string: {string_representation}" @staticmethod def test_collection_delitem(collection_test_data): @@ -199,6 +208,17 @@ def test_collection_delitem(collection_test_data): with pytest.raises(KeyError): object_collection[ref_id] + @staticmethod + def test_unstored_parent_access_raises(collection_test_data): + """Test that unstored objects raise an error when accessing the parent.""" + object_collection, _, object_ids = collection_test_data + ref_id = object_ids[0] + object = object_collection[ref_id].clone() + with pytest.raises(RuntimeError) as exc: + object.parent + assert "unstored" in str(exc.value) + assert "parent" in str(exc.value) + class NoLockedMixin(TreeObjectTester): @pytest.fixture diff --git a/tests/unittests/test_analysis_ply.py b/tests/unittests/test_analysis_ply.py index 005f351370..56cbe636f5 100644 --- a/tests/unittests/test_analysis_ply.py +++ b/tests/unittests/test_analysis_ply.py @@ -78,11 +78,17 @@ def properties_3_layers(self, model): }, } + @staticmethod @pytest.fixture - def collection_test_data(self, model: Model): + def parent_object(model: Model): add_stackup_with_3_layers_to_modeling_ply(model) production_ply = get_first_modeling_ply(model).production_plies["ProductionPly"] + return production_ply + + @pytest.fixture + def collection_test_data(self, parent_object): + production_ply = parent_object object_collection = getattr(production_ply, self.COLLECTION_NAME) object_collection.values() object_names = ["P1L1__ModelingPly.1", "P1L2__ModelingPly.1", "P1L3__ModelingPly.1"] diff --git a/tests/unittests/test_cad_component.py b/tests/unittests/test_cad_component.py index 19ecaf2392..8cce9b4ca2 100644 --- a/tests/unittests/test_cad_component.py +++ b/tests/unittests/test_cad_component.py @@ -32,7 +32,7 @@ def model(load_model_from_tempfile): @pytest.fixture -def cad_geometry(model, load_cad_geometry): +def parent_object(model, load_cad_geometry): with load_cad_geometry(model) as cad_geometry: yield cad_geometry @@ -41,6 +41,7 @@ class TestCADComponent(TreeObjectTesterReadOnly): COLLECTION_NAME = "cad_components" @pytest.fixture - def collection_test_data(self, model, cad_geometry): + def collection_test_data(self, model, parent_object): + cad_geometry = parent_object model.update() return cad_geometry.root_shapes, ["SOLID", "SHELL"], ["SOLID", "SHELL"] diff --git a/tests/unittests/test_edge_property_types.py b/tests/unittests/test_edge_property_types.py new file mode 100644 index 0000000000..bd7052df14 --- /dev/null +++ b/tests/unittests/test_edge_property_types.py @@ -0,0 +1,83 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from pytest_cases import parametrize_with_cases + +from ansys.acp.core import ( + BooleanOperationType, + FabricWithAngle, + Lamina, + LinkedSelectionRule, + SubShape, + TaperEdge, +) + + +def case_fabric_with_angle(load_model_from_tempfile): + with load_model_from_tempfile() as model: + fabric = model.create_fabric() + yield FabricWithAngle(fabric=fabric, angle=12.3), ("fabric", "angle") + + +def case_linked_selection_rule(load_model_from_tempfile): + with load_model_from_tempfile() as model: + selection_rule = model.create_parallel_selection_rule() + yield LinkedSelectionRule( + selection_rule=selection_rule, + operation_type=BooleanOperationType.ADD, + template_rule=False, + parameter_1=1.0, + parameter_2=2.0, + ), ("selection_rule", "operation_type", "template_rule", "parameter_1", "parameter_2") + + +def case_taper_edge(load_model_from_tempfile): + with load_model_from_tempfile() as model: + edge_set = model.create_edge_set() + yield TaperEdge(edge_set=edge_set, angle=11.2, offset=0.6), ("edge_set", "angle", "offset") + + +def case_subshape(load_model_from_tempfile): + with load_model_from_tempfile() as model: + cad_geometry = model.create_cad_geometry() + yield SubShape(cad_geometry=cad_geometry, path="path/to/subshape"), ("cad_geometry", "path") + + +def case_lamina_fabric(load_model_from_tempfile): + with load_model_from_tempfile() as model: + material = model.create_fabric() + yield Lamina(material=material, angle=7.5), ("material", "angle") + + +def case_lamina_stackup(load_model_from_tempfile): + with load_model_from_tempfile() as model: + material = model.create_stackup() + yield Lamina(material=material, angle=7.5), ("material", "angle") + + +@parametrize_with_cases("edge_property_type_instance,attribute_names", cases=".", glob="*") +def test_clone(edge_property_type_instance, attribute_names): + cloned_instance = edge_property_type_instance.clone() + for attr_name in attribute_names: + assert getattr(cloned_instance, attr_name) == getattr( + edge_property_type_instance, attr_name + ) diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index d32bf6b368..eb86bff3d2 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -268,3 +268,9 @@ def test_modeling_ply_export(acp_instance, minimal_complete_model, xfail_before) minimal_complete_model.export_modeling_ply_geometries(out_file_path) acp_instance.download_file(out_file_path, local_file_path) assert local_file_path.exists() + + +def test_parent_access_raises(minimal_complete_model): + with pytest.raises(RuntimeError) as exc: + minimal_complete_model.parent + assert "parent" in str(exc.value) diff --git a/tests/unittests/test_recursive_copy.py b/tests/unittests/test_recursive_copy.py new file mode 100644 index 0000000000..14bf0afdb7 --- /dev/null +++ b/tests/unittests/test_recursive_copy.py @@ -0,0 +1,308 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import numpy as np +import pytest + +from ansys.acp.core import FabricWithAngle, recursive_copy + + +@pytest.fixture +def minimal_complete_model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +def test_basic_recursive_copy(minimal_complete_model): + """Test copying a Modeling Ply and its linked objects.""" + # GIVEN: A Modeling Ply with linked objects + mg = minimal_complete_model.modeling_groups["ModelingGroup.1"] + mp = mg.modeling_plies["ModelingPly.1"] + + # WHEN: Recursively copying the Modeling Ply onto the same Modeling Group + new_objects = recursive_copy( + source_objects=[mp], + parent_mapping={mg: mg, minimal_complete_model: minimal_complete_model}, + linked_object_handling="copy", + ) + + # THEN: The expected new objects are created + assert len(new_objects) == 6 + assert {obj.id for obj in new_objects.values()} == { # type: ignore + "Global Coordinate System.2", + "All_Elements.2", + "Structural Steel.2", + "Fabric.2", + "OrientedSelectionSet.2", + "ModelingPly.2", + } + + +def test_basic_recursive_copy_keep_links(minimal_complete_model): + """Test copying a Modeling Ply without linked objects.""" + # GIVEN: A Modeling Ply with linked objects + mg = minimal_complete_model.modeling_groups["ModelingGroup.1"] + mp = mg.modeling_plies["ModelingPly.1"] + + # WHEN: Recursively copying the Modeling Ply onto the same Modeling Group, without linked objects + new_objects = recursive_copy( + source_objects=[mp], parent_mapping={mg: mg}, linked_object_handling="keep" + ) + + # THEN: The expected new objects are created, with the links kept + assert len(new_objects) == 1 + assert {obj.id for obj in new_objects.values()} == { # type: ignore + "ModelingPly.2", + } + assert list(new_objects.values())[0].ply_material.id == "Fabric.1" # type: ignore + + +def test_basic_recursive_copy_no_links(minimal_complete_model): + """Test copying a Modeling Ply without linked objects.""" + # GIVEN: A Modeling Ply with linked objects + mg = minimal_complete_model.modeling_groups["ModelingGroup.1"] + mp = mg.modeling_plies["ModelingPly.1"] + + # WHEN: Recursively copying the Modeling Ply onto the same Modeling Group, without linked objects + new_objects = recursive_copy( + source_objects=[mp], parent_mapping={mg: mg}, linked_object_handling="discard" + ) + + # THEN: The expected new objects are created, without links + assert len(new_objects) == 1 + assert {obj.id for obj in new_objects.values()} == { # type: ignore + "ModelingPly.2", + } + assert list(new_objects.values())[0].ply_material is None # type: ignore + + +def test_copy_all_objects(minimal_complete_model): + """Test copying all objects on a model onto itself.""" + # GIVEN: A simple model + model = minimal_complete_model + + # WHEN: Recursively copying, starting from the root of the model + new_objects = recursive_copy(source_objects=[model], parent_mapping={model: model}) + + # THEN: The expected new objects are created + # NOTE: This list may need to be updated when the model reference file + # is changed. + assert len(new_objects) == 8 + assert {obj.id for obj in new_objects.values()} == { # type: ignore + "Global Coordinate System.2", + "All_Elements.2", + "ns_edge.2", + "Structural Steel.2", + "Fabric.2", + "OrientedSelectionSet.2", + "ModelingGroup.2", + "ModelingPly.2", + } + + +def test_copy_to_different_model(minimal_complete_model, load_model_from_tempfile): + """Test copying all objects on a model onto a different model.""" + # GIVEN: Two models + model1 = minimal_complete_model + with load_model_from_tempfile() as model2: + + # WHEN: Recursively copying all objects from model1 to model2 + new_objects = recursive_copy( + source_objects=[model1], parent_mapping={model1: model2}, linked_object_handling="copy" + ) + + # THEN: The expected new objects are created + # NOTE: This list may need to be updated when the model reference file + # is changed. + assert len(new_objects) == 8 + assert {obj.id for obj in new_objects.values()} == { # type: ignore + "Global Coordinate System.2", + "All_Elements.2", + "ns_edge.2", + "Structural Steel.2", + "Fabric.2", + "OrientedSelectionSet.2", + "ModelingGroup.2", + "ModelingPly.2", + } + + +def test_copy_edge_property_list(minimal_complete_model): + """Test copying an object which has an Edge Property List.""" + # GIVEN: A simple model with a Stackup + model = minimal_complete_model + fabric1 = model.fabrics["Fabric.1"] + fabric2 = model.create_fabric(name="other_fabric", material=model.materials["Structural Steel"]) + stackup = model.create_stackup( + name="Stackup.1", + fabrics=[ + FabricWithAngle(fabric=fabric1, angle=0), + FabricWithAngle(fabric=fabric2, angle=90), + ], + ) + + # WHEN: Recursively copying the Stackup + new_objects = recursive_copy( + source_objects=[stackup], parent_mapping={model: model}, linked_object_handling="copy" + ) + + # THEN: + # - The stackup, fabrics, and materials are copied + # - The copied stackup links the new fabrics with the correct order and angles + assert len(new_objects) == 4 + assert {obj.id for obj in new_objects.values()} == { # type: ignore + "Stackup.2", + "Fabric.2", + "other_fabric.2", + "Structural Steel.2", + } + new_stackup = model.stackups["Stackup.2"] + linked_fabrics = new_stackup.fabrics + assert [fabric_with_angle.fabric.id for fabric_with_angle in linked_fabrics] == [ + "Fabric.2", + "other_fabric.2", + ] + assert [fabric_with_angle.angle for fabric_with_angle in linked_fabrics] == [0, 90] + + +def test_missing_parent_object_raises(minimal_complete_model): + """Test that an exception is raised if the parent_mapping is incomplete""" + + # GIVEN: A simple model + mg = minimal_complete_model.modeling_groups["ModelingGroup.1"] + mp = mg.modeling_plies["ModelingPly.1"] + + # WHEN: Recursively copying a Modeling Ply without providing the Model in + # the parent_mapping (needed due to links to e.g. the Global Coordinate System) + # THEN: An exception is raised + with pytest.raises(KeyError) as exc_info: + recursive_copy( + source_objects=[mp], + parent_mapping={mg: mg}, + linked_object_handling="copy", + ) + + msg = str(exc_info.value) + assert "Parent object" in msg + assert "parent_mapping" in msg + + +def test_copy_lookup_table_with_columns(minimal_complete_model): + """Test copying lookup tables with columns and their data. + + This case is special because LUT implement a check for the shape + of the data in their columns, which is determined by the "Location" + column. + """ + # GIVEN: a model which has objects with sub-attributes + # (here: a lookup table with columns) + model = minimal_complete_model + lut = model.create_lookup_table_1d() + lut.columns["Location"].data = [1.0, 2.0, 3.0] + lut.create_column(name="column1", data=[4.0, 5.0, 6.0]) + + # WHEN: recursively copying the lookup table + new_objects = recursive_copy(source_objects=[lut], parent_mapping={model: model}) + + # THEN: the expected new objects are created, but the sub-attributes are + # not explicitly copied + assert len(new_objects) == 2 + assert {obj.id for obj in new_objects.values()} == { # type: ignore + "LookUpTable1D.2", + "column1", + } + new_lut = model.lookup_tables_1d["LookUpTable1D.2"] + assert np.allclose(new_lut.columns["Location"].data, [1.0, 2.0, 3.0]) + assert np.allclose(new_lut.columns["column1"].data, [4.0, 5.0, 6.0]) + + +def test_inconsistent_source_model(minimal_complete_model, load_model_from_tempfile): + """Test that an exception is raised if the source objects are not all from the same model.""" + # GIVEN: Two models + model1 = minimal_complete_model + with load_model_from_tempfile() as model2: + # WHEN: Copying objects from different models + # THEN: An exception is raised + with pytest.raises(ValueError) as exc: + recursive_copy( + source_objects=[model1, model2], + parent_mapping={model1: model2}, + linked_object_handling="copy", + ) + assert "source_objects" in str(exc.value) + assert "'parent_mapping' keys" in str(exc.value) + assert "same model" in str(exc.value) + + +def test_inconsistent_source_model_2(minimal_complete_model, load_model_from_tempfile): + """Test that an exception is raised if the source objects are not all from the same model.""" + # GIVEN: Two models + model1 = minimal_complete_model + with load_model_from_tempfile() as model2: + # WHEN: Copying objects from different models + # THEN: An exception is raised + with pytest.raises(ValueError) as exc: + recursive_copy( + source_objects=[model1], + parent_mapping={model2: model2}, # parent_mapping keys are from the wrong model + linked_object_handling="copy", + ) + assert "source_objects" in str(exc.value) + assert "'parent_mapping' keys" in str(exc.value) + assert "same model" in str(exc.value) + + +def test_inconsistent_target_model(minimal_complete_model, load_model_from_tempfile): + """Test that an exception is raised if the target parents are not all from the same model.""" + # GIVEN: Two models + model1 = minimal_complete_model + mat = model1.materials["Structural Steel"] + with load_model_from_tempfile() as model2: + # WHEN: Copying objects from different models + # THEN: An exception is raised + with pytest.raises(ValueError) as exc: + recursive_copy( + source_objects=[model1], + parent_mapping={model1: model2, mat: mat}, + linked_object_handling="copy", + ) + assert "parent_mapping" in str(exc.value) + assert "'parent_mapping' values" in str(exc.value) + assert "same model" in str(exc.value) + + +def test_keep_links_across_models_raises(minimal_complete_model, load_model_from_tempfile): + """Test that an exception is raised when trying to keep links across models.""" + # GIVEN: Two models + model1 = minimal_complete_model + with load_model_from_tempfile() as model2: + # WHEN: Copying objects across models, with linked_object_handling="keep" + # THEN: An exception is raised + with pytest.raises(ValueError) as exc: + recursive_copy( + source_objects=[model1], + parent_mapping={model1: model2}, + linked_object_handling="keep", + ) + assert "linked_object_handling" in str(exc.value) + assert "keep" in str(exc.value) + assert "copy objects between models" in str(exc.value) diff --git a/tests/unittests/test_workflow.py b/tests/unittests/test_workflow.py index e92ade4f61..1654a6d98f 100644 --- a/tests/unittests/test_workflow.py +++ b/tests/unittests/test_workflow.py @@ -24,6 +24,7 @@ import shutil import tempfile +from packaging.version import parse as parse_version import pytest from ansys.acp.core import ACPWorkflow, UnitSystemType @@ -102,10 +103,16 @@ def test_workflow_unit_system_dat(acp_instance, model_data_dir, unit_system): input_file_path = model_data_dir / "flat_plate_input.dat" - if unit_system != UnitSystemType.UNDEFINED: + # Initializing a workflow with a defined unit system is not allowed if the + # input file does contain the unit system. + # Since 25.1, we also allow it if the unit systems match. + if parse_version(acp_instance.server_version) < parse_version("25.1"): + allowed_unit_system_values = [UnitSystemType.UNDEFINED] + else: + allowed_unit_system_values = [UnitSystemType.UNDEFINED, UnitSystemType.MKS] + + if unit_system not in allowed_unit_system_values: with pytest.raises(ValueError) as ex: - # Initializing a workflow with a defined unit system is not allowed - # if the input file does contain the unit system. ACPWorkflow.from_cdb_or_dat_file( acp=acp_instance, cdb_or_dat_file_path=input_file_path, From 57ec18ef491dc03897c318c208647e80d5ce4622 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:16:19 +0200 Subject: [PATCH 10/96] Bump the dependencies group with 3 updates (#601) Bumps the dependencies group with 3 updates: [types-protobuf](https://github.com/python/typeshed), [sphinx-autodoc-typehints](https://github.com/tox-dev/sphinx-autodoc-typehints) and [ansys-sphinx-theme](https://github.com/ansys/ansys-sphinx-theme). Updates `types-protobuf` from 5.27.0.20240907 to 5.27.0.20240920 - [Commits](https://github.com/python/typeshed/commits) Updates `sphinx-autodoc-typehints` from 2.4.1 to 2.4.4 - [Release notes](https://github.com/tox-dev/sphinx-autodoc-typehints/releases) - [Changelog](https://github.com/tox-dev/sphinx-autodoc-typehints/blob/main/CHANGELOG.md) - [Commits](https://github.com/tox-dev/sphinx-autodoc-typehints/compare/2.4.1...2.4.4) Updates `ansys-sphinx-theme` from 1.0.9 to 1.0.11 - [Release notes](https://github.com/ansys/ansys-sphinx-theme/releases) - [Commits](https://github.com/ansys/ansys-sphinx-theme/compare/v1.0.9...v1.0.11) --- updated-dependencies: - dependency-name: types-protobuf dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: sphinx-autodoc-typehints dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: ansys-sphinx-theme dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index e85f831867..18776d22b6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -470,13 +470,13 @@ clr-loader = ">=0.2.6,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.0.9" +version = "1.0.11" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.9" files = [ - {file = "ansys_sphinx_theme-1.0.9-py3-none-any.whl", hash = "sha256:583769355cf8a0d779ce7388bb66792a03f5f9f4ed0d8d8998469d2d2a563ada"}, - {file = "ansys_sphinx_theme-1.0.9.tar.gz", hash = "sha256:e5190aba2cc5dec0163d7b86d352c9d87a39f6b3e23cd7cea58cf154e0565d3f"}, + {file = "ansys_sphinx_theme-1.0.11-py3-none-any.whl", hash = "sha256:75c718e407a6b1e868ed5063ba3d1ac93c778195e129253dbaf2367bb87940f5"}, + {file = "ansys_sphinx_theme-1.0.11.tar.gz", hash = "sha256:a9c3399f71fbdfee08dc97eb6821e660742b7f0c425a3a6f443254a356863ed4"}, ] [package.dependencies] @@ -4267,13 +4267,13 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools [[package]] name = "sphinx-autodoc-typehints" -version = "2.4.1" +version = "2.4.4" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false python-versions = ">=3.10" files = [ - {file = "sphinx_autodoc_typehints-2.4.1-py3-none-any.whl", hash = "sha256:af37abb816ebd2cf56c7a8174fd2f34d0f2f84fbf58265f89429ae107212fe6f"}, - {file = "sphinx_autodoc_typehints-2.4.1.tar.gz", hash = "sha256:cfe410920cecf08ade046bb387b0007edb83e992de59686c62d194c762f1e45c"}, + {file = "sphinx_autodoc_typehints-2.4.4-py3-none-any.whl", hash = "sha256:940de2951fd584d147e46772579fdc904f945c5f1ee1a78c614646abfbbef18b"}, + {file = "sphinx_autodoc_typehints-2.4.4.tar.gz", hash = "sha256:e743512da58b67a06579a1462798a6907664ab77460758a43234adeac350afbf"}, ] [package.dependencies] @@ -4674,13 +4674,13 @@ trame-client = "*" [[package]] name = "types-protobuf" -version = "5.27.0.20240907" +version = "5.27.0.20240920" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types-protobuf-5.27.0.20240907.tar.gz", hash = "sha256:bb6f90f66b18d4d1c75667b6586334b0573a6fcee5eb0142a7348a765a7cbadc"}, - {file = "types_protobuf-5.27.0.20240907-py3-none-any.whl", hash = "sha256:5443270534cc8072909ef7ad9e1421ccff924ca658749a6396c0c43d64c32676"}, + {file = "types-protobuf-5.27.0.20240920.tar.gz", hash = "sha256:992d695315d11eb2d25e806122c9e1fd9fec282e96104f0a0cb9226cd5d90293"}, + {file = "types_protobuf-5.27.0.20240920-py3-none-any.whl", hash = "sha256:c04140bd3c761a55f4e661372b24a6f508169e0815f2b73da33f34b447ed7a8d"}, ] [[package]] From 76c6de81302f6f5ad0738ed1839aa618404b3650 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:53:48 +0200 Subject: [PATCH 11/96] Bump ansys/actions from 7 to 8 (#613) Bumps [ansys/actions](https://github.com/ansys/actions) from 7 to 8. - [Release notes](https://github.com/ansys/actions/releases) - [Commits](https://github.com/ansys/actions/compare/v7...v8) --- updated-dependencies: - dependency-name: ansys/actions dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_cd.yml | 14 +++++++------- .github/workflows/package_cleanup.yml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 807758ea23..36d521c5ac 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -83,7 +83,7 @@ jobs: name: "Documentation style" runs-on: ubuntu-latest steps: - - uses: ansys/actions/doc-style@v7 + - uses: ansys/actions/doc-style@v8 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -101,7 +101,7 @@ jobs: os: macos-latest steps: - name: "Build wheelhouse and perform smoke test" - uses: ansys/actions/build-wheelhouse@v7 + uses: ansys/actions/build-wheelhouse@v8 with: library-name: ${{ env.PACKAGE_NAME }} operating-system: ${{ matrix.os }} @@ -436,7 +436,7 @@ jobs: timeout-minutes: 30 steps: - name: Build library source and wheel artifacts - uses: ansys/actions/build-library@v7 + uses: ansys/actions/build-library@v8 with: library-name: ${{ env.PACKAGE_NAME }} python-version: ${{ env.MAIN_PYTHON_VERSION }} @@ -448,14 +448,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Release to the public PyPI repository - uses: ansys/actions/release-pypi-public@v7 + uses: ansys/actions/release-pypi-public@v8 with: library-name: ${{ env.PACKAGE_NAME }} twine-username: "__token__" twine-token: ${{ secrets.PYPI_TOKEN }} - name: Release to GitHub - uses: ansys/actions/release-github@v7 + uses: ansys/actions/release-github@v8 with: library-name: ${{ env.PACKAGE_NAME }} @@ -466,7 +466,7 @@ jobs: needs: [build] steps: - name: Deploy the latest documentation - uses: ansys/actions/doc-deploy-dev@v7 + uses: ansys/actions/doc-deploy-dev@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} @@ -479,7 +479,7 @@ jobs: needs: [release] steps: - name: Deploy the stable documentation - uses: ansys/actions/doc-deploy-stable@v7 + uses: ansys/actions/doc-deploy-stable@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package_cleanup.yml b/.github/workflows/package_cleanup.yml index 7e95459378..b3ab76f83b 100644 --- a/.github/workflows/package_cleanup.yml +++ b/.github/workflows/package_cleanup.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Delete untagged package versions" - uses: ansys/actions/hk-package-clean-untagged@v7 + uses: ansys/actions/hk-package-clean-untagged@v8 with: package-org: 'ansys' package-name: 'acp' From 8fcae810a66eb21a2f9b1cef740f38dd3d39f5ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:23:37 +0200 Subject: [PATCH 12/96] Bump the dependencies group across 1 directory with 13 updates (#616) Bumps the dependencies group with 13 updates in the / directory: | Package | From | To | | --- | --- | --- | | [networkx](https://github.com/networkx/networkx) | `3.3` | `3.4.1` | | [ansys-mapdl-core](https://github.com/ansys/pymapdl) | `0.68.4` | `0.68.6` | | [pre-commit](https://github.com/pre-commit/pre-commit) | `3.8.0` | `4.0.1` | | [black](https://github.com/psf/black) | `24.8.0` | `24.10.0` | | [mypy](https://github.com/python/mypy) | `1.11.2` | `1.12.0` | | [types-protobuf](https://github.com/python/typeshed) | `5.27.0.20240920` | `5.28.0.20240924` | | [sphinx](https://github.com/sphinx-doc/sphinx) | `8.0.2` | `8.1.3` | | [sphinx-autodoc-typehints](https://github.com/tox-dev/sphinx-autodoc-typehints) | `2.4.4` | `2.5.0` | | [ansys-sphinx-theme](https://github.com/ansys/ansys-sphinx-theme) | `1.0.11` | `1.1.5` | | [pypandoc](https://github.com/JessicaTegner/pypandoc) | `1.13` | `1.14` | | [sphinx-gallery](https://github.com/sphinx-gallery/sphinx-gallery) | `0.17.1` | `0.18.0` | | [pytest-cases](https://github.com/smarie/python-pytest-cases) | `3.8.5` | `3.8.6` | | [hypothesis](https://github.com/HypothesisWorks/hypothesis) | `6.112.1` | `6.115.2` | Updates `networkx` from 3.3 to 3.4.1 - [Release notes](https://github.com/networkx/networkx/releases) - [Commits](https://github.com/networkx/networkx/compare/networkx-3.3...networkx-3.4.1) Updates `ansys-mapdl-core` from 0.68.4 to 0.68.6 - [Release notes](https://github.com/ansys/pymapdl/releases) - [Changelog](https://github.com/ansys/pymapdl/blob/main/CHANGELOG.md) - [Commits](https://github.com/ansys/pymapdl/compare/v0.68.4...v0.68.6) Updates `pre-commit` from 3.8.0 to 4.0.1 - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.8.0...v4.0.1) Updates `black` from 24.8.0 to 24.10.0 - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/24.8.0...24.10.0) Updates `mypy` from 1.11.2 to 1.12.0 - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.11.2...v1.12.0) Updates `types-protobuf` from 5.27.0.20240920 to 5.28.0.20240924 - [Commits](https://github.com/python/typeshed/commits) Updates `sphinx` from 8.0.2 to 8.1.3 - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.0.2...v8.1.3) Updates `sphinx-autodoc-typehints` from 2.4.4 to 2.5.0 - [Release notes](https://github.com/tox-dev/sphinx-autodoc-typehints/releases) - [Changelog](https://github.com/tox-dev/sphinx-autodoc-typehints/blob/main/CHANGELOG.md) - [Commits](https://github.com/tox-dev/sphinx-autodoc-typehints/compare/2.4.4...2.5.0) Updates `ansys-sphinx-theme` from 1.0.11 to 1.1.5 - [Release notes](https://github.com/ansys/ansys-sphinx-theme/releases) - [Commits](https://github.com/ansys/ansys-sphinx-theme/compare/v1.0.11...v1.1.5) Updates `pypandoc` from 1.13 to 1.14 - [Release notes](https://github.com/JessicaTegner/pypandoc/releases) - [Changelog](https://github.com/JessicaTegner/pypandoc/blob/master/release.md) - [Commits](https://github.com/JessicaTegner/pypandoc/compare/v1.13...v1.14) Updates `sphinx-gallery` from 0.17.1 to 0.18.0 - [Release notes](https://github.com/sphinx-gallery/sphinx-gallery/releases) - [Changelog](https://github.com/sphinx-gallery/sphinx-gallery/blob/master/.github_changelog_generator) - [Commits](https://github.com/sphinx-gallery/sphinx-gallery/compare/v0.17.1...v0.18.0) Updates `pytest-cases` from 3.8.5 to 3.8.6 - [Release notes](https://github.com/smarie/python-pytest-cases/releases) - [Changelog](https://github.com/smarie/python-pytest-cases/blob/main/docs/changelog.md) - [Commits](https://github.com/smarie/python-pytest-cases/compare/3.8.5...3.8.6) Updates `hypothesis` from 6.112.1 to 6.115.2 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.112.1...hypothesis-python-6.115.2) --- updated-dependencies: - dependency-name: networkx dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: ansys-mapdl-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-major dependency-group: dependencies - dependency-name: black dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: types-protobuf dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: sphinx dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: sphinx-autodoc-typehints dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: ansys-sphinx-theme dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: pypandoc dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: sphinx-gallery dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: pytest-cases dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 362 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 252 insertions(+), 110 deletions(-) diff --git a/poetry.lock b/poetry.lock index 18776d22b6..d4f8fe40e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -294,13 +294,13 @@ plotting = ["imageio (<2.28.1)", "imageio-ffmpeg", "matplotlib (>=3.2)", "pyvist [[package]] name = "ansys-mapdl-core" -version = "0.68.4" +version = "0.68.6" description = "A Python wrapper for Ansys MAPDL." optional = false -python-versions = "<3.13,>=3.9" +python-versions = "<3.13,>=3.10" files = [ - {file = "ansys_mapdl_core-0.68.4-py3-none-any.whl", hash = "sha256:66c2e8421b52f9b85518795cfc5a6ee9379947c72555c8687ec70445c1a7d5eb"}, - {file = "ansys_mapdl_core-0.68.4.tar.gz", hash = "sha256:5d98199c3ab37316261c7ab60f335a46c7b569576eb439d76757007158b7627d"}, + {file = "ansys_mapdl_core-0.68.6-py3-none-any.whl", hash = "sha256:8a62178e7b0aeb71a40edd4b97cfc2fa7849df643ad574f41b52aebea552b2f8"}, + {file = "ansys_mapdl_core-0.68.6.tar.gz", hash = "sha256:cb387b4f6a251f8da1ce0f5a6cca73b2ad83cdd8574fdb1da102280cb95c9f44"}, ] [package.dependencies] @@ -309,27 +309,27 @@ ansys-mapdl-reader = ">=0.51.7" ansys-math-core = ">=0.1.2" ansys-platform-instancemanagement = ">=1.0,<2.0" ansys-tools-path = ">=0.3.1" +ansys-tools-visualization-interface = ">=0.2.6" click = ">=8.1.3" grpcio = ">=1.30.0" importlib-metadata = ">=4.0" matplotlib = ">=3.0.0" -numpy = {version = ">=1.14.0,<2.0.0", markers = "python_version >= \"3.9\""} +numpy = {version = ">=1.14.0,<3.0.0", markers = "python_version >= \"3.9\""} pexpect = {version = ">=4.8.0", markers = "platform_system == \"Linux\""} platformdirs = ">=3.6.0" protobuf = ">=3.12.2" psutil = ">=5.9.4" pyansys-tools-versioning = ">=0.3.3" pyiges = {version = ">=0.3.1", extras = ["full"]} -pyvista = ">=0.38.1" scipy = ">=1.3.0" tabulate = ">=0.8.0" tqdm = ">=4.45.0" vtk = ">=9.0.0" [package.extras] -doc = ["ansys-dpf-core (==0.10.1)", "ansys-mapdl-reader (==0.53.0)", "ansys-sphinx-theme (==0.16.6)", "grpcio (==1.65.0)", "imageio (==2.34.2)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.1)", "numpydoc (==1.7.0)", "pandas (==2.2.2)", "plotly (==5.22.0)", "pyiges[full] (==0.3.1)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista[jupyter] (==0.43.10)", "sphinx (==7.3.7)", "sphinx-autobuild (==2024.4.16)", "sphinx-autodoc-typehints (==1.25.2)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.0)", "sphinx-gallery (==0.16.0)", "sphinx-notfound-page (==1.0.2)", "sphinxcontrib-websupport (==1.2.7)", "sphinxemoji (==0.3.1)", "vtk (==9.3.1)"] -jupyter = ["ipywidgets", "jupyterlab (>=3)", "pyvista[jupyter]"] -tests = ["ansys-dpf-core (==0.10.1)", "autopep8 (==2.3.1)", "matplotlib (==3.9.1)", "pandas (==2.2.2)", "pyansys-tools-report (==0.7.3)", "pyiges[full] (==0.3.1)", "pytest (==8.2.2)", "pytest-cov (==5.0.0)", "pytest-memprof (<0.3.0)", "pytest-pyvista (==0.1.9)", "pytest-rerunfailures (==14.0)", "pyvista (==0.43.10)", "scipy (==1.14.0)", "vtk (==9.3.1)"] +doc = ["ansys-dpf-core (==0.10.1)", "ansys-mapdl-reader (==0.54.1)", "ansys-sphinx-theme (==1.1.2)", "ansys-tools-visualization-interface (==0.4.5)", "grpcio (==1.66.2)", "imageio (==2.35.1)", "imageio-ffmpeg (==0.5.1)", "jupyter (==1.1.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "nbformat (==5.10.4)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "plotly (==5.24.1)", "pyiges[full] (==0.3.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "sphinx (==8.1.0)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==1.25.2)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-gallery (==0.18.0)", "sphinx-jinja (==2.0.2)", "sphinx-notfound-page (==1.0.4)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)", "vtk (==9.3.1)"] +jupyter = ["ipywidgets", "jupyterlab (>=3)"] +tests = ["ansys-dpf-core (==0.10.1)", "ansys-tools-visualization-interface (==0.4.5)", "autopep8 (==2.3.1)", "matplotlib (==3.9.2)", "pandas (==2.2.3)", "pyansys-tools-report (==0.8.0)", "pyiges[full] (==0.3.1)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-memprof (<0.3.0)", "pytest-pyvista (==0.1.9)", "pytest-rerunfailures (==14.0)", "scipy (==1.14.1)", "vtk (==9.3.1)"] [[package]] name = "ansys-mapdl-reader" @@ -470,13 +470,13 @@ clr-loader = ">=0.2.6,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.0.11" +version = "1.1.5" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" files = [ - {file = "ansys_sphinx_theme-1.0.11-py3-none-any.whl", hash = "sha256:75c718e407a6b1e868ed5063ba3d1ac93c778195e129253dbaf2367bb87940f5"}, - {file = "ansys_sphinx_theme-1.0.11.tar.gz", hash = "sha256:a9c3399f71fbdfee08dc97eb6821e660742b7f0c425a3a6f443254a356863ed4"}, + {file = "ansys_sphinx_theme-1.1.5-py3-none-any.whl", hash = "sha256:43e24af005b9ff84f83279839f24fe61faa5fe3d882b01134366043e7ad08315"}, + {file = "ansys_sphinx_theme-1.1.5.tar.gz", hash = "sha256:c1e36732f424597945ae45583c92fe69dcca0ecb67300e5dc95a48c63052baa4"}, ] [package.dependencies] @@ -487,8 +487,8 @@ pydata-sphinx-theme = ">=0.15.4,<0.16" Sphinx = ">=4.2.0" [package.extras] -autoapi = ["sphinx-autoapi (==3.2.1)", "sphinx-design (==0.6.1)", "sphinx-jinja (==2.0.2)"] -doc = ["Pillow (>=9.0)", "PyGitHub (==2.3.0)", "Sphinx (==8.0.2)", "jupytext (==1.16.4)", "nbsphinx (==0.9.5)", "notebook (==7.2.1)", "numpydoc (==1.8.0)", "pandas (==2.2.2)", "pyvista[jupyter] (==0.44.1)", "requests (==2.32.3)", "sphinx-autoapi (==3.2.1)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-gallery (==0.17.1)", "sphinx-jinja (==2.0.2)", "sphinx-notfound-page (==1.0.4)"] +autoapi = ["sphinx-autoapi (==3.3.2)", "sphinx-design (==0.6.1)", "sphinx-jinja (==2.0.2)"] +doc = ["Pillow (>=9.0)", "PyGitHub (==2.4.0)", "Sphinx (==8.0.2)", "jupytext (==1.16.4)", "nbsphinx (==0.9.5)", "notebook (==7.2.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "pyvista[jupyter] (==0.44.1)", "requests (==2.32.3)", "sphinx-autoapi (==3.3.2)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-gallery (==0.17.1)", "sphinx-jinja (==2.0.2)", "sphinx-notfound-page (==1.0.4)"] [[package]] name = "ansys-tools-filetransfer" @@ -543,6 +543,29 @@ build = ["build (==1.2.1)", "twine (==5.1.0)"] doc = ["Sphinx (==7.3.7)", "ansys-sphinx-theme (==0.16.2)", "numpydoc (==1.7.0)", "sphinx-copybutton (==0.5.2)"] tests = ["pyfakefs (==5.5.0)", "pytest (==8.2.1)", "pytest-cov (==5.0.0)"] +[[package]] +name = "ansys-tools-visualization-interface" +version = "0.4.5" +description = "A Python visualization interface for PyAnsys libraries" +optional = false +python-versions = "<4,>=3.9" +files = [ + {file = "ansys_tools_visualization_interface-0.4.5-py3-none-any.whl", hash = "sha256:ac8d32be63ccf736a2c5246d4c3564aa38dcfffbf54db4ece41d5ac6abab4e8a"}, + {file = "ansys_tools_visualization_interface-0.4.5.tar.gz", hash = "sha256:3eef6907b5aa109230aa23185b14f6071ccebcb60a4a5dd4e706692700857f63"}, +] + +[package.dependencies] +beartype = ">=0.17.0,<1" +pyvista = ">=0.43.0,<1" +trame = ">=3.6.0,<4" +trame-vtk = ">=2.8.7,<3" +trame-vuetify = ">=2.4.3,<3" +websockets = ">=12.0,<14" + +[package.extras] +doc = ["ansys-fluent-core (==0.24.2)", "ansys-sphinx-theme (==0.16.6)", "jupyter_sphinx (==0.5.3)", "jupytext (==1.16.3)", "nbsphinx (==0.9.4)", "numpydoc (==1.7.0)", "sphinx (==7.4.7)", "sphinx-autoapi (==3.2.1)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.17.0)", "sphinx-jinja (==2.0.2)", "sphinx_design (==0.6.0)"] +tests = ["pytest (==8.3.2)", "pytest-cov (==5.0.0)", "pytest-pyvista (==0.1.9)"] + [[package]] name = "anyio" version = "4.4.0" @@ -740,6 +763,24 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[[package]] +name = "beartype" +version = "0.19.0" +description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "beartype-0.19.0-py3-none-any.whl", hash = "sha256:33b2694eda0daf052eb2aff623ed9a8a586703bbf0a90bbc475a83bbf427f699"}, + {file = "beartype-0.19.0.tar.gz", hash = "sha256:de42dfc1ba5c3710fde6c3002e3bd2cad236ed4d2aabe876345ab0b4234a6573"}, +] + +[package.extras] +dev = ["autoapi (>=0.9.0)", "coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] +doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] +test = ["coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] +test-tox = ["equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "typing-extensions (>=3.10.0.0)"] +test-tox-coverage = ["coverage (>=5.5)"] + [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -763,33 +804,33 @@ lxml = ["lxml"] [[package]] name = "black" -version = "24.8.0" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -803,7 +844,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -1805,13 +1846,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.112.1" +version = "6.115.2" description = "A library for property-based testing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "hypothesis-6.112.1-py3-none-any.whl", hash = "sha256:93631b1498b20d2c205ed304cbd41d50e9c069d78a9c773c1324ca094c5e30ce"}, - {file = "hypothesis-6.112.1.tar.gz", hash = "sha256:b070d7a1bb9bd84706c31885c9aeddc138e2b36a9c112a91984f49501c567856"}, + {file = "hypothesis-6.115.2-py3-none-any.whl", hash = "sha256:d0ccbd67f588a6abcdc17a4f06fea3b2bc0a166ecd89126fb90985036c1940fe"}, + {file = "hypothesis-6.115.2.tar.gz", hash = "sha256:6df95ea6cccf950f80e142f43a684d1462e26c5e28ba29ab4eceb8399e207e0b"}, ] [package.dependencies] @@ -1820,21 +1861,21 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.70)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.13)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] +all = ["black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.74)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.16)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.19.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] -crosshair = ["crosshair-tool (>=0.0.70)", "hypothesis-crosshair (>=0.0.13)"] +crosshair = ["crosshair-tool (>=0.0.74)", "hypothesis-crosshair (>=0.0.16)"] dateutil = ["python-dateutil (>=1.4)"] -django = ["django (>=3.2)"] +django = ["django (>=4.2)"] dpcontracts = ["dpcontracts (>=0.4)"] ghostwriter = ["black (>=19.10b0)"] lark = ["lark (>=0.10.1)"] -numpy = ["numpy (>=1.17.3)"] +numpy = ["numpy (>=1.19.3)"] pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] -zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2024.1)"] +zoneinfo = ["tzdata (>=2024.2)"] [[package]] name = "identify" @@ -2754,38 +2795,43 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.11.2" +version = "1.12.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, - {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, - {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, - {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, - {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, - {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, - {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, - {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, - {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, - {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, - {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, - {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, - {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, - {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, - {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, - {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, - {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, - {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, - {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, + {file = "mypy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4397081e620dc4dc18e2f124d5e1d2c288194c2c08df6bdb1db31c38cd1fe1ed"}, + {file = "mypy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:684a9c508a283f324804fea3f0effeb7858eb03f85c4402a967d187f64562469"}, + {file = "mypy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cabe4cda2fa5eca7ac94854c6c37039324baaa428ecbf4de4567279e9810f9e"}, + {file = "mypy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:060a07b10e999ac9e7fa249ce2bdcfa9183ca2b70756f3bce9df7a92f78a3c0a"}, + {file = "mypy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0eff042d7257f39ba4ca06641d110ca7d2ad98c9c1fb52200fe6b1c865d360ff"}, + {file = "mypy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b86de37a0da945f6d48cf110d5206c5ed514b1ca2614d7ad652d4bf099c7de7"}, + {file = "mypy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c7c5ce0c1be0b0aea628374e6cf68b420bcc772d85c3c974f675b88e3e6e57"}, + {file = "mypy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a64ee25f05fc2d3d8474985c58042b6759100a475f8237da1f4faf7fcd7e6309"}, + {file = "mypy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faca7ab947c9f457a08dcb8d9a8664fd438080e002b0fa3e41b0535335edcf7f"}, + {file = "mypy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:5bc81701d52cc8767005fdd2a08c19980de9ec61a25dbd2a937dfb1338a826f9"}, + {file = "mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164"}, + {file = "mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475"}, + {file = "mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9"}, + {file = "mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642"}, + {file = "mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601"}, + {file = "mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521"}, + {file = "mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893"}, + {file = "mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721"}, + {file = "mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3"}, + {file = "mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b"}, + {file = "mypy-1.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eafc1b7319b40ddabdc3db8d7d48e76cfc65bbeeafaa525a4e0fa6b76175467f"}, + {file = "mypy-1.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b9ce1ad8daeb049c0b55fdb753d7414260bad8952645367e70ac91aec90e07e"}, + {file = "mypy-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfe012b50e1491d439172c43ccb50db66d23fab714d500b57ed52526a1020bb7"}, + {file = "mypy-1.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c40658d4fa1ab27cb53d9e2f1066345596af2f8fe4827defc398a09c7c9519b"}, + {file = "mypy-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:dee78a8b9746c30c1e617ccb1307b351ded57f0de0d287ca6276378d770006c0"}, + {file = "mypy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b5df6c8a8224f6b86746bda716bbe4dbe0ce89fd67b1fa4661e11bfe38e8ec8"}, + {file = "mypy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5feee5c74eb9749e91b77f60b30771563327329e29218d95bedbe1257e2fe4b0"}, + {file = "mypy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77278e8c6ffe2abfba6db4125de55f1024de9a323be13d20e4f73b8ed3402bd1"}, + {file = "mypy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dcfb754dea911039ac12434d1950d69a2f05acd4d56f7935ed402be09fad145e"}, + {file = "mypy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:06de0498798527451ffb60f68db0d368bd2bae2bbfb5237eae616d4330cc87aa"}, + {file = "mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266"}, + {file = "mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d"}, ] [package.dependencies] @@ -2903,20 +2949,21 @@ files = [ [[package]] name = "networkx" -version = "3.3" +version = "3.4.1" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" files = [ - {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, - {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, + {file = "networkx-3.4.1-py3-none-any.whl", hash = "sha256:e30a87b48c9a6a7cc220e732bffefaee585bdb166d13377734446ce1a0620eed"}, + {file = "networkx-3.4.1.tar.gz", hash = "sha256:f9df45e85b78f5bd010993e897b4f1fdb242c11e015b101bd951e5c0e29982d8"}, ] [package.extras] -default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] @@ -3234,13 +3281,13 @@ xxhash = ["xxhash (>=1.4.3)"] [[package]] name = "pre-commit" -version = "3.8.0" +version = "4.0.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, + {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, + {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, ] [package.dependencies] @@ -3495,13 +3542,13 @@ full = ["geomdl", "pyvista (>=0.28.0)"] [[package]] name = "pypandoc" -version = "1.13" +version = "1.14" description = "Thin wrapper for pandoc." optional = false python-versions = ">=3.6" files = [ - {file = "pypandoc-1.13-py3-none-any.whl", hash = "sha256:4c7d71bf2f1ed122aac287113b5c4d537a33bbc3c1df5aed11a7d4a7ac074681"}, - {file = "pypandoc-1.13.tar.gz", hash = "sha256:31652073c7960c2b03570bd1e94f602ca9bc3e70099df5ead4cea98ff5151c1e"}, + {file = "pypandoc-1.14-py3-none-any.whl", hash = "sha256:1315c7ad7fac7236dacf69a05b521ed2c3f1d0177f70e9b92bfffce6c023df22"}, + {file = "pypandoc-1.14.tar.gz", hash = "sha256:6b4c45f5f1b9fb5bb562079164806bdbbc3e837b5402bcf3f1139edc5730a197"}, ] [[package]] @@ -3562,13 +3609,13 @@ histogram = ["pygal", "pygaljs"] [[package]] name = "pytest-cases" -version = "3.8.5" +version = "3.8.6" description = "Separate test code from test cases in pytest." optional = false python-versions = "*" files = [ - {file = "pytest-cases-3.8.5.tar.gz", hash = "sha256:c92054187847a7d30d8ab6709fb1670a830e4e19234be700c85c96b6d7102c16"}, - {file = "pytest_cases-3.8.5-py2.py3-none-any.whl", hash = "sha256:0c8e3727f6494e65b47321adbd94c767277c7b5de1bd40dd518a13f7443b1fff"}, + {file = "pytest_cases-3.8.6-py2.py3-none-any.whl", hash = "sha256:564c722492ea7e7ec3ac433fd14070180e65866f49fa35bfe938c0d5d9afba67"}, + {file = "pytest_cases-3.8.6.tar.gz", hash = "sha256:5c24e0ab0cb6f8e802a469b7965906a333d3babb874586ebc56f7e2cbe1a7c44"}, ] [package.dependencies] @@ -4232,13 +4279,13 @@ files = [ [[package]] name = "sphinx" -version = "8.0.2" +version = "8.1.3" description = "Python documentation generator" optional = false python-versions = ">=3.10" files = [ - {file = "sphinx-8.0.2-py3-none-any.whl", hash = "sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"}, - {file = "sphinx-8.0.2.tar.gz", hash = "sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b"}, + {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, + {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, ] [package.dependencies] @@ -4252,28 +4299,28 @@ packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" +sphinxcontrib-applehelp = ">=1.0.7" +sphinxcontrib-devhelp = ">=1.0.6" +sphinxcontrib-htmlhelp = ">=2.0.6" +sphinxcontrib-jsmath = ">=1.0.1" +sphinxcontrib-qthelp = ">=1.0.6" sphinxcontrib-serializinghtml = ">=1.1.9" tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=6.0)", "mypy (==1.11.0)", "pytest (>=6.0)", "ruff (==0.5.5)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240520)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20240724)", "types-requests (>=2.30.0)"] +lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-autodoc-typehints" -version = "2.4.4" +version = "2.5.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false python-versions = ">=3.10" files = [ - {file = "sphinx_autodoc_typehints-2.4.4-py3-none-any.whl", hash = "sha256:940de2951fd584d147e46772579fdc904f945c5f1ee1a78c614646abfbbef18b"}, - {file = "sphinx_autodoc_typehints-2.4.4.tar.gz", hash = "sha256:e743512da58b67a06579a1462798a6907664ab77460758a43234adeac350afbf"}, + {file = "sphinx_autodoc_typehints-2.5.0-py3-none-any.whl", hash = "sha256:53def4753239683835b19bfa8b68c021388bd48a096efcb02cdab508ece27363"}, + {file = "sphinx_autodoc_typehints-2.5.0.tar.gz", hash = "sha256:259e1026b218d563d72743f417fcc25906a9614897fe37f91bd8d7d58f748c3b"}, ] [package.dependencies] @@ -4329,13 +4376,13 @@ theme-sbt = ["sphinx-book-theme (>=1.1,<2.0)"] [[package]] name = "sphinx-gallery" -version = "0.17.1" +version = "0.18.0" description = "A Sphinx extension that builds an HTML gallery of examples from any set of Python scripts." optional = false python-versions = ">=3.8" files = [ - {file = "sphinx_gallery-0.17.1-py3-none-any.whl", hash = "sha256:0a1142a15a9d63169fe7b12167dc028891fb8db31bfc6d7de03ba0d68d591830"}, - {file = "sphinx_gallery-0.17.1.tar.gz", hash = "sha256:c9969abcc5ca8c24496014da8260833b8c3ccdb32c17716b5ba66f2e0a3cc183"}, + {file = "sphinx_gallery-0.18.0-py3-none-any.whl", hash = "sha256:54317366e77b182672797e5b46ab13cca9a27eafc3142c59dc4c211d4afe3420"}, + {file = "sphinx_gallery-0.18.0.tar.gz", hash = "sha256:4b5b5bc305348c01d00cf66ad852cfd2dd8b67f7f32ae3e2820c01557b3f92f9"}, ] [package.dependencies] @@ -4674,13 +4721,13 @@ trame-client = "*" [[package]] name = "types-protobuf" -version = "5.27.0.20240920" +version = "5.28.0.20240924" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types-protobuf-5.27.0.20240920.tar.gz", hash = "sha256:992d695315d11eb2d25e806122c9e1fd9fec282e96104f0a0cb9226cd5d90293"}, - {file = "types_protobuf-5.27.0.20240920-py3-none-any.whl", hash = "sha256:c04140bd3c761a55f4e661372b24a6f508169e0815f2b73da33f34b447ed7a8d"}, + {file = "types-protobuf-5.28.0.20240924.tar.gz", hash = "sha256:d181af8a256e5a91ce8d5adb53496e880efd9144c7d54483e3653332b60296f0"}, + {file = "types_protobuf-5.28.0.20240924-py3-none-any.whl", hash = "sha256:5cecf612ccdefb7dc95f7a51fb502902f20fc2e6681cd500184aaa1b3931d6a7"}, ] [[package]] @@ -4861,6 +4908,101 @@ docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] +[[package]] +name = "websockets" +version = "13.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, + {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, + {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, + {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, + {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, + {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, + {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, + {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, + {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, + {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, + {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, + {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, + {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, + {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"}, + {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"}, + {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"}, + {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"}, + {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"}, + {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"}, + {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, + {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"}, + {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"}, + {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"}, + {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, + {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, +] + [[package]] name = "widgetsnbextension" version = "4.0.13" From 4befd0e376868a3d140742b16472077895a2851a Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 15 Oct 2024 17:36:23 +0200 Subject: [PATCH 13/96] Update AUTHORS and CONTRIBUTORS.md (#618) --- AUTHORS | 4 ++-- CONTRIBUTORS.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4e1f434d47..f0532d79ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,8 +5,8 @@ # For contributions made under a Corporate CLA, the organization is # added to this file. # -# If you have contributed to the repository and wish to be added to this file -# please submit a request. +# If you have contributed to the repository and want to be added to this file, +# submit a request. # # ANSYS, Inc. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index fc53569ac3..fefd506689 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ # Contributors -## Project Lead or Owner +## Project Lead * [Dominik Gresch](https://github.com/greschd) * [Jan von Rickenbach](https://github.com/janvonrickenbach) From 29f3e8838e5783317aa4e0b592e4d51bf0363812 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 16 Oct 2024 14:18:11 +0200 Subject: [PATCH 14/96] Adapt tests to stricter units handling (#619) --- tests/unittests/test_model.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index eb86bff3d2..b762f4033e 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -31,7 +31,7 @@ import pytest import pyvista -from ansys.acp.core import ElementalDataType, VectorData +from ansys.acp.core import ElementalDataType, UnitSystemType, VectorData from .helpers import check_property @@ -152,7 +152,10 @@ def test_string_representation(acp_instance, model_data_dir): input_file_path = model_data_dir / "ACP-Pre.h5" remote_file_path = acp_instance.upload_file(input_file_path) model = acp_instance.import_model( - name="minimal_model", path=remote_file_path, format="ansys:cdb" + name="minimal_model", + path=remote_file_path, + format="ansys:cdb", + unit_system=UnitSystemType.MKS, ) assert repr(model) == "" From f8b93d76770b87318232d8d699a4642ddf06457f Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 16 Oct 2024 14:29:34 +0200 Subject: [PATCH 15/96] Add required inputs for doc deploy (#617) Add the newly-required inputs for the v8 version of the doc deploy action. See https://actions.docs.ansys.com/version/dev/migrations/index.html#migration-guide Replace GITHUB_TOKEN with PYANSYS_CI_BOT_TOKEN where applicable, so that the commiter's credentials match the used email / username. --- .github/workflows/ci_cd.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 36d521c5ac..4758907d15 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -469,8 +469,10 @@ jobs: uses: ansys/actions/doc-deploy-dev@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} force-orphan: false + bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} upload_docs_release: name: Upload release documentation @@ -482,4 +484,6 @@ jobs: uses: ansys/actions/doc-deploy-stable@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} + bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} From bc6aa18a9bed264d07584618ccfaf189ef93c3fb Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 16 Oct 2024 14:55:57 +0200 Subject: [PATCH 16/96] Add 'supported_since' keywords to the gRPC property helpers (#603) Allow marking gRPC properties as supported since a specific server version. The `grpc_data_property_read_only` is given a `supported_since` keyword, and `grpc_data_property` is given two separate keywords `readable_since` and `writable_since`. Other changes: - Change the `xfail_before` test fixture to `raises_before_version`, which explicitly checks that a `RuntimeError` is raised when run on an older server version. - Move the `supported_since` implementation to a separate file. - In the CI definition, reuse the `DOCKER_IMAGE_NAME` variable in more places. --- .github/workflows/ci_cd.yml | 4 +- .../_grpc_helpers/property_helper.py | 98 ++++++++++++++----- .../_tree_objects/_grpc_helpers/protocols.py | 4 + .../_grpc_helpers/supported_since.py | 84 ++++++++++++++++ src/ansys/acp/core/_tree_objects/base.py | 39 ++------ src/ansys/acp/core/_tree_objects/model.py | 3 +- tests/conftest.py | 8 +- tests/unittests/test_model.py | 11 ++- 8 files changed, 186 insertions(+), 65 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 4758907d15..048c42ed7f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -170,7 +170,7 @@ jobs: poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html env: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} - IMAGE_NAME: "ghcr.io/ansys/acp${{ github.event.inputs.docker_image_suffix || ':latest' }}" + IMAGE_NAME: ${{ env.DOCKER_IMAGE_NAME }} - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v4 @@ -279,7 +279,7 @@ jobs: run: > poetry run ansys-launcher configure ACP docker_compose - --image_name_pyacp=ghcr.io/ansys/acp${{ github.event.inputs.docker_image_suffix || ':latest' }} + --image_name_pyacp=${{ env.DOCKER_IMAGE_NAME }} --image_name_filetransfer=ghcr.io/ansys/tools-filetransfer:latest --license_server=1055@$LICENSE_SERVER --keep_volume=False diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py index 50bac3c7e4..9f3ae90735 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py @@ -38,6 +38,7 @@ from ..._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty from .polymorphic_from_pb import CreatableFromResourcePath, tree_object_from_resource_path from .protocols import Editable, GrpcObjectBase, ObjectInfo, Readable +from .supported_since import supported_since as supported_since_decorator # Note: The typing of the protobuf objects is fairly loose, maybe it could # be improved. The main challenge is that we do not encode the structure of @@ -110,7 +111,10 @@ def inner(self: Readable) -> CreatableFromResourcePath | None: def grpc_data_getter( - name: str, from_protobuf: _FROM_PROTOBUF_T[_GET_T], check_optional: bool = False + name: str, + from_protobuf: _FROM_PROTOBUF_T[_GET_T], + check_optional: bool = False, + supported_since: str | None = None, ) -> Callable[[Readable], _GET_T]: """Create a getter method which obtains the server object via the gRPC Get endpoint. @@ -125,6 +129,14 @@ def grpc_data_getter( will be used. """ + @supported_since_decorator( + supported_since, + # The default error message uses 'inner' as the method name, which is confusing + err_msg_tpl=( + f"The property '{name.split('.')[-1]}' is only readable since version {{required_version}} " + f"of the ACP gRPC server. The current server version is {{server_version}}." + ), + ) def inner(self: Readable) -> Any: self._get_if_stored() pb_attribute = _get_data_attribute(self._pb_object, name, check_optional=check_optional) @@ -149,26 +161,6 @@ def inner(self: Editable, value: Readable | None) -> None: return inner -def grpc_data_setter( - name: str, to_protobuf: _TO_PROTOBUF_T[_SET_T] -) -> Callable[[Editable, _SET_T], None]: - """Create a setter method which updates the server object via the gRPC Put endpoint.""" - - def inner(self: Editable, value: _SET_T) -> None: - self._get_if_stored() - current_value = _get_data_attribute(self._pb_object, name) - value_pb = to_protobuf(value) - try: - needs_updating = current_value != value_pb - except TypeError: - needs_updating = True - if needs_updating: - _set_data_attribute(self._pb_object, name, value_pb) - self._put_if_stored() - - return inner - - def _get_data_attribute(pb_obj: Message, name: str, check_optional: bool = False) -> _PROTOBUF_T: name_parts = name.split(".") if check_optional: @@ -197,6 +189,37 @@ def _set_data_attribute(pb_obj: ObjectInfo, name: str, value: _PROTOBUF_T) -> No target_object.add().CopyFrom(item) +def grpc_data_setter( + name: str, + to_protobuf: _TO_PROTOBUF_T[_SET_T], + setter_func: Callable[[ObjectInfo, str, _PROTOBUF_T], None] = _set_data_attribute, + supported_since: str | None = None, +) -> Callable[[Editable, _SET_T], None]: + """Create a setter method which updates the server object via the gRPC Put endpoint.""" + + @supported_since_decorator( + supported_since, + # The default error message uses 'inner' as the method name, which is confusing + err_msg_tpl=( + f"The property '{name.split('.')[-1]}' is only editable since version {{required_version}} " + f"of the ACP gRPC server. The current server version is {{server_version}}." + ), + ) + def inner(self: Editable, value: _SET_T) -> None: + self._get_if_stored() + current_value = _get_data_attribute(self._pb_object, name) + value_pb = to_protobuf(value) + try: + needs_updating = current_value != value_pb + except TypeError: + needs_updating = True + if needs_updating: + setter_func(self._pb_object, name, value_pb) + self._put_if_stored() + + return inner + + AnyT = TypeVar("AnyT") @@ -212,6 +235,9 @@ def grpc_data_property( from_protobuf: _FROM_PROTOBUF_T[_GET_T] = lambda x: x, check_optional: bool = False, doc: str | None = None, + setter_func: Callable[[ObjectInfo, str, _PROTOBUF_T], None] = _set_data_attribute, + readable_since: str | None = None, + writable_since: str | None = None, ) -> ReadWriteProperty[_GET_T, _SET_T]: """Define a property which is synchronized with the backend via gRPC. @@ -234,6 +260,10 @@ def grpc_data_property( will be used. doc : Docstring for the property. + readable_since : + Version since which the property is supported for reading. + writable_since : + Version since which the property is supported for setting. """ # Note jvonrick August 2023: We don't ensure with typechecks that the property returned here is # compatible with the class on which this property is created. For example: @@ -244,8 +274,20 @@ def grpc_data_property( # https://github.com/python/typing/issues/985 return _wrap_doc( _exposed_grpc_property( - grpc_data_getter(name, from_protobuf=from_protobuf, check_optional=check_optional) - ).setter(grpc_data_setter(name, to_protobuf=to_protobuf)), + grpc_data_getter( + name, + from_protobuf=from_protobuf, + check_optional=check_optional, + supported_since=readable_since, + ) + ).setter( + grpc_data_setter( + name, + to_protobuf=to_protobuf, + setter_func=setter_func, + supported_since=writable_since, + ) + ), doc=doc, ) @@ -255,6 +297,7 @@ def grpc_data_property_read_only( from_protobuf: _FROM_PROTOBUF_T[_GET_T] = lambda x: x, check_optional: bool = False, doc: str | None = None, + supported_since: str | None = None, ) -> ReadOnlyProperty[_GET_T]: """Define a read-only property which is synchronized with the backend via gRPC. @@ -275,10 +318,17 @@ def grpc_data_property_read_only( will be used. doc : Docstring for the property. + supported_since : + Version since which the property is supported. """ return _wrap_doc( _exposed_grpc_property( - grpc_data_getter(name, from_protobuf=from_protobuf, check_optional=check_optional) + grpc_data_getter( + name, + from_protobuf=from_protobuf, + check_optional=check_optional, + supported_since=supported_since, + ) ), doc=doc, ) diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py index fdc2410dda..35bfdea392 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py @@ -29,6 +29,7 @@ from google.protobuf.message import Message import grpc +from packaging.version import Version from ansys.api.acp.v0.base_pb2 import ( BasicInfo, @@ -188,6 +189,9 @@ def _resource_path(self) -> ResourcePath: ... _pb_object: Any + @property + def _server_version(self) -> Version | None: ... + class Editable(Readable, Protocol): """Interface definition for editable objects.""" diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py new file mode 100644 index 0000000000..e27b63ff5e --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py @@ -0,0 +1,84 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from collections.abc import Callable +from functools import wraps +from typing import Concatenate, TypeAlias, TypeVar + +from packaging.version import parse as parse_version +from typing_extensions import ParamSpec + +from .protocols import Readable + +T = TypeVar("T", bound=Readable) +P = ParamSpec("P") +R = TypeVar("R") +_WRAPPED_T: TypeAlias = Callable[Concatenate[T, P], R] + + +def supported_since( + version: str | None, err_msg_tpl: str | None = None +) -> Callable[[_WRAPPED_T[T, P, R]], _WRAPPED_T[T, P, R]]: + """Mark a TreeObjectBase method as supported since a specific server version. + + Raises an exception if the current server version does not match the required version. + If either the given `version` or the server version is `None`, the decorator does nothing. + + Parameters + ---------- + version : Optional[str] + The server version since which the method is supported. If ``None``, the + decorator does nothing. + err_msg_tpl : Optional[str] + A custom error message template. If ``None``, a default error message is used. + """ + if version is None: + # return a trivial decorator if no version is specified + def trivial_decorator(func: _WRAPPED_T[T, P, R]) -> _WRAPPED_T[T, P, R]: + return func + + return trivial_decorator + + required_version = parse_version(version) + + def decorator(func: _WRAPPED_T[T, P, R]) -> _WRAPPED_T[T, P, R]: + @wraps(func) + def inner(self: T, /, *args: P.args, **kwargs: P.kwargs) -> R: + server_version = self._server_version + # If the object is not stored, we cannot check the server version. + if server_version is not None: + if server_version < required_version: + if err_msg_tpl is None: + err_msg = ( + f"The method '{func.__name__}' is only supported since version {version} " + f"of the ACP gRPC server. The current server version is {server_version}." + ) + else: + err_msg = err_msg_tpl.format( + required_version=required_version, server_version=server_version + ) + raise RuntimeError(err_msg) + return func(self, *args, **kwargs) + + return inner + + return decorator diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index 758f993719..8e0a11133f 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -26,14 +26,13 @@ from abc import abstractmethod from collections.abc import Callable, Iterable from dataclasses import dataclass -from functools import wraps import typing -from typing import Any, Concatenate, Generic, TypeAlias, TypeVar, cast +from typing import Any, Generic, TypeVar, cast from grpc import Channel from packaging.version import Version from packaging.version import parse as parse_version -from typing_extensions import ParamSpec, Self +from typing_extensions import Self from ansys.api.acp.v0.base_pb2 import CollectionPath, DeleteRequest, GetRequest, ResourcePath @@ -147,6 +146,12 @@ def _server_wrapper(self) -> ServerWrapper: assert self._server_wrapper_store is not None return self._server_wrapper_store + @property + def _server_version(self) -> Version | None: + if not self._is_stored: + return None + return self._server_wrapper.version + @property def _is_stored(self) -> bool: return self._server_wrapper_store is not None @@ -478,34 +483,6 @@ def _put_if_stored(self) -> None: self._put() -T = TypeVar("T", bound=TreeObjectBase) -P = ParamSpec("P") -R = TypeVar("R") -_WRAPPED_T: TypeAlias = Callable[Concatenate[T, P], R] - - -def supported_since(version: str) -> Callable[[_WRAPPED_T[T, P, R]], _WRAPPED_T[T, P, R]]: - """Mark a TreeObjectBase method as supported since a specific server version. - - Raises an exception if the current server version does not match the required version. - """ - required_version = parse_version(version) - - def decorator(func: _WRAPPED_T[T, P, R]) -> _WRAPPED_T[T, P, R]: - @wraps(func) - def inner(self: T, /, *args: P.args, **kwargs: P.kwargs) -> R: - if self._server_wrapper.version < required_version: - raise RuntimeError( - f"The method '{func.__name__}' is only supported since version {version} of the ACP " - f"gRPC server. The current server version is {self._server_wrapper.version}." - ) - return func(self, *args, **kwargs) - - return inner - - return decorator - - if typing.TYPE_CHECKING: # pragma: no cover # Ensure that the ReadOnlyTreeObject satisfies the Gettable interface _x: Readable = typing.cast(ReadOnlyTreeObject, None) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index b4fec9c781..3efca801ba 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -79,6 +79,7 @@ grpc_data_property_read_only, mark_grpc_properties, ) +from ._grpc_helpers.supported_since import supported_since from ._mesh_data import ( ElementalData, NodalData, @@ -87,7 +88,7 @@ elemental_data_property, nodal_data_property, ) -from .base import ServerWrapper, TreeObject, supported_since +from .base import ServerWrapper, TreeObject from .boolean_selection_rule import BooleanSelectionRule from .cad_geometry import CADGeometry from .cutoff_selection_rule import CutoffSelectionRule diff --git a/tests/conftest.py b/tests/conftest.py index e09540c005..d6b22b2828 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -264,11 +264,15 @@ def inner(model, relative_file_path="square_and_solid.stp"): @pytest.fixture -def xfail_before(acp_instance): +def raises_before_version(acp_instance): """Mark a test as expected to fail before a certain server version.""" + @contextmanager def inner(version: str): if parse_version(acp_instance.server_version) < parse_version(version): - pytest.xfail(f"Expected to fail until server version {version!r}") + with pytest.raises(RuntimeError): + yield + else: + yield return inner diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index b762f4033e..70ee7143e0 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -254,12 +254,11 @@ def test_regression_454(minimal_complete_model): assert not hasattr(minimal_complete_model, "store") -def test_modeling_ply_export(acp_instance, minimal_complete_model, xfail_before): +def test_modeling_ply_export(acp_instance, minimal_complete_model, raises_before_version): """ Test that the 'export_modeling_ply_geometries' method produces a file. The contents of the file are not checked. """ - xfail_before("25.1") out_filename = "modeling_ply_export.step" with tempfile.TemporaryDirectory() as tmp_dir: @@ -268,9 +267,11 @@ def test_modeling_ply_export(acp_instance, minimal_complete_model, xfail_before) out_file_path = pathlib.Path(out_filename) else: out_file_path = local_file_path - minimal_complete_model.export_modeling_ply_geometries(out_file_path) - acp_instance.download_file(out_file_path, local_file_path) - assert local_file_path.exists() + + with raises_before_version("25.1"): + minimal_complete_model.export_modeling_ply_geometries(out_file_path) + acp_instance.download_file(out_file_path, local_file_path) + assert local_file_path.exists() def test_parent_access_raises(minimal_complete_model): From 0a03f698809ac688811dc5d84b72826cfeaae179 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 16 Oct 2024 15:15:33 +0200 Subject: [PATCH 17/96] Allow marking 'supported_since' on class level (#604) Add a `_SUPPORTED_SINCE` class attribute to the `GrpcObjectBase` class, which has two effects: - upon storing an object, check the server version and raise an appropriate exception if the server version is too low - in the `mark_grpc_properties` class decorator, add a line at the end of the class docstring indicating the server version at which the class was introduced On all existing classes, the `_SUPPORTED_SINCE` attribute is set to `"24.2"`. --- src/ansys/acp/core/_server/direct.py | 5 +++++ .../_tree_objects/_grpc_helpers/mapping.py | 9 ++++++++ .../_grpc_helpers/property_helper.py | 22 +++++++++++++++++++ .../_tree_objects/_grpc_helpers/protocols.py | 1 + .../_grpc_helpers/supported_since.py | 6 ++--- .../acp/core/_tree_objects/analysis_ply.py | 1 + src/ansys/acp/core/_tree_objects/base.py | 8 +++++++ .../_tree_objects/boolean_selection_rule.py | 1 + .../acp/core/_tree_objects/cad_component.py | 1 + .../acp/core/_tree_objects/cad_geometry.py | 1 + .../_tree_objects/cutoff_selection_rule.py | 1 + .../cylindrical_selection_rule.py | 1 + src/ansys/acp/core/_tree_objects/edge_set.py | 1 + .../acp/core/_tree_objects/element_set.py | 1 + src/ansys/acp/core/_tree_objects/fabric.py | 1 + .../geometrical_selection_rule.py | 1 + .../_tree_objects/linked_selection_rule.py | 2 ++ .../acp/core/_tree_objects/lookup_table_1d.py | 1 + .../_tree_objects/lookup_table_1d_column.py | 1 + .../acp/core/_tree_objects/lookup_table_3d.py | 1 + .../_tree_objects/lookup_table_3d_column.py | 1 + .../core/_tree_objects/material/material.py | 1 + .../material/property_sets/density.py | 2 ++ .../property_sets/engineering_constants.py | 2 ++ .../property_sets/fabric_fiber_angle.py | 2 ++ .../material/property_sets/larc_constants.py | 2 ++ .../material/property_sets/puck_constants.py | 2 ++ .../material/property_sets/strain_limits.py | 2 ++ .../material/property_sets/stress_limits.py | 2 ++ .../property_sets/tsai_wu_constants.py | 2 ++ .../property_sets/woven_characterization.py | 2 ++ .../property_sets/woven_stress_limits.py | 2 ++ src/ansys/acp/core/_tree_objects/model.py | 1 + .../acp/core/_tree_objects/modeling_group.py | 1 + .../acp/core/_tree_objects/modeling_ply.py | 3 +++ .../_tree_objects/oriented_selection_set.py | 1 + .../_tree_objects/parallel_selection_rule.py | 1 + .../acp/core/_tree_objects/production_ply.py | 1 + src/ansys/acp/core/_tree_objects/rosette.py | 1 + src/ansys/acp/core/_tree_objects/sensor.py | 1 + .../_tree_objects/spherical_selection_rule.py | 1 + src/ansys/acp/core/_tree_objects/stackup.py | 3 +++ .../acp/core/_tree_objects/sublaminate.py | 3 +++ .../core/_tree_objects/tube_selection_rule.py | 1 + .../variable_offset_selection_rule.py | 1 + .../core/_tree_objects/virtual_geometry.py | 3 +++ 46 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/ansys/acp/core/_server/direct.py b/src/ansys/acp/core/_server/direct.py index 3c5dafb24e..a28b6319eb 100644 --- a/src/ansys/acp/core/_server/direct.py +++ b/src/ansys/acp/core/_server/direct.py @@ -22,6 +22,7 @@ import dataclasses import os +import pathlib import subprocess from typing import TextIO @@ -103,6 +104,10 @@ def start(self) -> None: stdout_file = self._config.stdout_file stderr_file = self._config.stderr_file + binary = pathlib.Path(self._config.binary_path) + if not binary.exists(): + raise FileNotFoundError(f"Binary not found: '{binary}'") + port = find_free_ports()[0] self._url = f"localhost:{port}" self._stdout = open(stdout_file, mode="w", encoding="utf-8") diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py index 5cb8353b74..95e176d96b 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py @@ -26,6 +26,7 @@ from typing import Any, Concatenate, Generic, TypeVar from grpc import Channel +from packaging.version import parse as parse_version from typing_extensions import ParamSpec, Self from ansys.api.acp.v0.base_pb2 import CollectionPath, DeleteRequest, ListRequest @@ -302,6 +303,14 @@ def define_mutable_mapping( """Define a mutable mapping of child tree objects.""" def collection_property(self: ParentT) -> MutableMapping[CreatableValueT]: + if self._server_version is not None: + if self._server_version < parse_version(object_class._SUPPORTED_SINCE): + raise RuntimeError( + f"The '{object_class.__name__}' object is only supported since version " + f"{object_class._SUPPORTED_SINCE} of the ACP gRPC server. The current server version is " + f"{self._server_version}." + ) + return MutableMapping._initialize_with_cache( server_wrapper=self._server_wrapper, collection_path=CollectionPath( diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py index 9f3ae90735..bcef7ddfba 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py @@ -29,6 +29,7 @@ from collections.abc import Callable from functools import reduce +import sys from typing import TYPE_CHECKING, Any, TypeVar from google.protobuf.message import Message @@ -91,6 +92,27 @@ def mark_grpc_properties(cls: T) -> T: if name not in props_unique: props_unique.append(name) cls._GRPC_PROPERTIES = tuple(props_unique) + + # The 'mark_grpc_properties' decorator is also used on intermediate base + # classes which do not have the '_SUPPORTED_SINCE' attribute. We only want + # to add the version information to the final class. + if hasattr(cls, "_SUPPORTED_SINCE"): + if isinstance(cls.__doc__, str): + # When adding to the docstring, we need to match the existing + # indentation of the docstring (except the first line). + # See PEP 257 'Handling Docstring Indentation'. + # Alternatively, we could strip the common indentation from the + # docstring. + indent = sys.maxsize + for line in cls.__doc__.splitlines()[1:]: + stripped = line.lstrip() + if stripped: # ignore empty lines + indent = min(indent, len(line) - len(stripped)) + if indent == sys.maxsize: + indent = 0 + cls.__doc__ += ( + f"\n\n{indent * ' '}*Added in ACP server version {cls._SUPPORTED_SINCE}.*\n" + ) return cls diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py index 35bfdea392..9ffeac391f 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/protocols.py @@ -151,6 +151,7 @@ class GrpcObjectBase(Protocol): __slots__: Iterable[str] = tuple() _GRPC_PROPERTIES: tuple[str, ...] = tuple() + _SUPPORTED_SINCE: str def __str__(self) -> str: string_items = [] diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py index e27b63ff5e..2b67cdf4b5 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/supported_since.py @@ -45,10 +45,10 @@ def supported_since( Parameters ---------- - version : Optional[str] + version : The server version since which the method is supported. If ``None``, the decorator does nothing. - err_msg_tpl : Optional[str] + err_msg_tpl : A custom error message template. If ``None``, a default error message is used. """ if version is None: @@ -69,7 +69,7 @@ def inner(self: T, /, *args: P.args, **kwargs: P.kwargs) -> R: if server_version < required_version: if err_msg_tpl is None: err_msg = ( - f"The method '{func.__name__}' is only supported since version {version} " + f"The '{func.__name__}' method is only supported since version {version} " f"of the ACP gRPC server. The current server version is {server_version}." ) else: diff --git a/src/ansys/acp/core/_tree_objects/analysis_ply.py b/src/ansys/acp/core/_tree_objects/analysis_ply.py index 485c427e07..dd4e64ce5c 100644 --- a/src/ansys/acp/core/_tree_objects/analysis_ply.py +++ b/src/ansys/acp/core/_tree_objects/analysis_ply.py @@ -105,6 +105,7 @@ class AnalysisPly(ReadOnlyTreeObject, IdTreeObject): _COLLECTION_LABEL = "analysis_plies" _OBJECT_INFO_TYPE = analysis_ply_pb2.ObjectInfo + _SUPPORTED_SINCE = "24.2" def _create_stub(self) -> analysis_ply_pb2_grpc.ObjectServiceStub: return analysis_ply_pb2_grpc.ObjectServiceStub(self._channel) diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index 8e0a11133f..045b24e50b 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -76,6 +76,7 @@ class TreeObjectBase(ObjectCacheMixin, GrpcObjectBase): _COLLECTION_LABEL: str _OBJECT_INFO_TYPE: type[ObjectInfo] + _SUPPORTED_SINCE: str _pb_object: ObjectInfo name: ReadOnlyProperty[str] @@ -328,6 +329,13 @@ def store(self: Self, parent: TreeObject) -> None: Parent object to store the object under. """ self._server_wrapper_store = parent._server_wrapper + assert self._server_version is not None + if self._server_version < parse_version(self._SUPPORTED_SINCE): + raise RuntimeError( + f"The '{type(self).__name__}' object is only supported since version " + f"{self._SUPPORTED_SINCE} of the ACP gRPC server. The current server version is " + f"{self._server_version}." + ) collection_path = CollectionPath( value=_rp_join(parent._resource_path.value, self._COLLECTION_LABEL) diff --git a/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py b/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py index 4688496ea2..d1f90f3edb 100644 --- a/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py @@ -91,6 +91,7 @@ class BooleanSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "boolean_selection_rules" _OBJECT_INFO_TYPE = boolean_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = boolean_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/cad_component.py b/src/ansys/acp/core/_tree_objects/cad_component.py index 6d88892eb3..586da9c437 100644 --- a/src/ansys/acp/core/_tree_objects/cad_component.py +++ b/src/ansys/acp/core/_tree_objects/cad_component.py @@ -49,6 +49,7 @@ class CADComponent(ReadOnlyTreeObject, IdTreeObject): __slots__: Iterable[str] = tuple() _COLLECTION_LABEL = "cad_components" _OBJECT_INFO_TYPE = cad_component_pb2.ObjectInfo + _SUPPORTED_SINCE = "24.2" def _create_stub(self) -> cad_component_pb2_grpc.ObjectServiceStub: return cad_component_pb2_grpc.ObjectServiceStub(self._channel) diff --git a/src/ansys/acp/core/_tree_objects/cad_geometry.py b/src/ansys/acp/core/_tree_objects/cad_geometry.py index 98bca072ff..53f18a035b 100644 --- a/src/ansys/acp/core/_tree_objects/cad_geometry.py +++ b/src/ansys/acp/core/_tree_objects/cad_geometry.py @@ -108,6 +108,7 @@ class CADGeometry(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "cad_geometries" _OBJECT_INFO_TYPE = cad_geometry_pb2.ObjectInfo _CREATE_REQUEST_TYPE = cad_geometry_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py b/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py index 390fbd5674..52ea3051b1 100644 --- a/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py @@ -115,6 +115,7 @@ class CutoffSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "cutoff_selection_rules" _OBJECT_INFO_TYPE = cutoff_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = cutoff_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py b/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py index b3e1e232c6..68fbfdfc49 100644 --- a/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py @@ -103,6 +103,7 @@ class CylindricalSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "cylindrical_selection_rules" _OBJECT_INFO_TYPE = cylindrical_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = cylindrical_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/edge_set.py b/src/ansys/acp/core/_tree_objects/edge_set.py index 636cd25787..a02dafe226 100644 --- a/src/ansys/acp/core/_tree_objects/edge_set.py +++ b/src/ansys/acp/core/_tree_objects/edge_set.py @@ -75,6 +75,7 @@ class EdgeSet(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "edge_sets" _OBJECT_INFO_TYPE = edge_set_pb2.ObjectInfo _CREATE_REQUEST_TYPE = edge_set_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/element_set.py b/src/ansys/acp/core/_tree_objects/element_set.py index 4fa67900dc..9c75f52bb6 100644 --- a/src/ansys/acp/core/_tree_objects/element_set.py +++ b/src/ansys/acp/core/_tree_objects/element_set.py @@ -88,6 +88,7 @@ class ElementSet(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "element_sets" _OBJECT_INFO_TYPE = element_set_pb2.ObjectInfo _CREATE_REQUEST_TYPE = element_set_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/fabric.py b/src/ansys/acp/core/_tree_objects/fabric.py index 246d5ff458..be75c5f56b 100644 --- a/src/ansys/acp/core/_tree_objects/fabric.py +++ b/src/ansys/acp/core/_tree_objects/fabric.py @@ -86,6 +86,7 @@ class Fabric(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "fabrics" _OBJECT_INFO_TYPE = fabric_pb2.ObjectInfo _CREATE_REQUEST_TYPE = fabric_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py b/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py index 68af4e1a25..b73720484f 100644 --- a/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py @@ -111,6 +111,7 @@ class GeometricalSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "geometrical_selection_rules" _OBJECT_INFO_TYPE = geometrical_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = geometrical_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py index 6dd88e71a4..2803e12d53 100644 --- a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py @@ -105,6 +105,8 @@ class LinkedSelectionRule(GenericEdgePropertyType): a Boolean Selection Rule, only to a Modeling Ply. """ + _SUPPORTED_SINCE = "24.2" + def __init__( self, selection_rule: _LINKABLE_SELECTION_RULE_TYPES, diff --git a/src/ansys/acp/core/_tree_objects/lookup_table_1d.py b/src/ansys/acp/core/_tree_objects/lookup_table_1d.py index 7dbb2f62b7..d1a8e39260 100644 --- a/src/ansys/acp/core/_tree_objects/lookup_table_1d.py +++ b/src/ansys/acp/core/_tree_objects/lookup_table_1d.py @@ -78,6 +78,7 @@ class LookUpTable1D(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "lookup_tables_1d" _OBJECT_INFO_TYPE = lookup_table_1d_pb2.ObjectInfo _CREATE_REQUEST_TYPE = lookup_table_1d_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py b/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py index 437f0a11f0..5ca0ab53fd 100644 --- a/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py +++ b/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py @@ -62,6 +62,7 @@ class LookUpTable1DColumn(LookUpTableColumnBase): _COLLECTION_LABEL = "lookup_table_1d_columns" _OBJECT_INFO_TYPE = lookup_table_1d_column_pb2.ObjectInfo _CREATE_REQUEST_TYPE = lookup_table_1d_column_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/lookup_table_3d.py b/src/ansys/acp/core/_tree_objects/lookup_table_3d.py index 78a3db0fa2..03b48b6807 100644 --- a/src/ansys/acp/core/_tree_objects/lookup_table_3d.py +++ b/src/ansys/acp/core/_tree_objects/lookup_table_3d.py @@ -87,6 +87,7 @@ class LookUpTable3D(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "lookup_tables_3d" _OBJECT_INFO_TYPE = lookup_table_3d_pb2.ObjectInfo _CREATE_REQUEST_TYPE = lookup_table_3d_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py b/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py index d34e9b56c1..29ecc9a504 100644 --- a/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py +++ b/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py @@ -62,6 +62,7 @@ class LookUpTable3DColumn(LookUpTableColumnBase): _COLLECTION_LABEL = "lookup_table_3d_columns" _OBJECT_INFO_TYPE = lookup_table_3d_column_pb2.ObjectInfo _CREATE_REQUEST_TYPE = lookup_table_3d_column_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/material/material.py b/src/ansys/acp/core/_tree_objects/material/material.py index 0cbf4ab40a..859c7022ed 100644 --- a/src/ansys/acp/core/_tree_objects/material/material.py +++ b/src/ansys/acp/core/_tree_objects/material/material.py @@ -128,6 +128,7 @@ class Material(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "materials" _OBJECT_INFO_TYPE = material_pb2.ObjectInfo _CREATE_REQUEST_TYPE = material_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/density.py b/src/ansys/acp/core/_tree_objects/material/property_sets/density.py index a5693511f2..aa3035c04e 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/density.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/density.py @@ -44,6 +44,7 @@ class ConstantDensity(_DensityMixin, _ConstantPropertySet): """Constant density material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" def __init__( self, @@ -65,5 +66,6 @@ class VariableDensity(_DensityMixin, _VariablePropertySet): """Variable density material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" rho = variable_material_grpc_data_property("rho") diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/engineering_constants.py b/src/ansys/acp/core/_tree_objects/material/property_sets/engineering_constants.py index bdfe87d58e..5845873371 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/engineering_constants.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/engineering_constants.py @@ -70,6 +70,7 @@ class ConstantEngineeringConstants(_EngineeringConstantsMixin, _ConstantProperty """Constant engineering constants material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" @classmethod def from_isotropic_constants( @@ -173,6 +174,7 @@ class VariableEngineeringConstants(_EngineeringConstantsMixin, _VariableProperty """Variable engineering constants material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" E = variable_material_grpc_data_property("E", **_ISOTROPIC_KWARGS) nu = variable_material_grpc_data_property("nu", **_ISOTROPIC_KWARGS) diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/fabric_fiber_angle.py b/src/ansys/acp/core/_tree_objects/material/property_sets/fabric_fiber_angle.py index a14f47cca7..ae84305398 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/fabric_fiber_angle.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/fabric_fiber_angle.py @@ -48,6 +48,7 @@ class ConstantFabricFiberAngle(_FabricFiberAngleMixin, _ConstantPropertySet): """ _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" def __init__( self, @@ -73,5 +74,6 @@ class VariableFabricFiberAngle(_FabricFiberAngleMixin, _VariablePropertySet): """ _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" fabric_fiber_angle = variable_material_grpc_data_property("fabric_fiber_angle") diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/larc_constants.py b/src/ansys/acp/core/_tree_objects/material/property_sets/larc_constants.py index ea3b606626..b5baf38527 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/larc_constants.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/larc_constants.py @@ -44,6 +44,7 @@ class ConstantLaRCConstants(_LaRCConstantsMixin, _ConstantPropertySet): """Constant LaRC failure criterion properties.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" def __init__( self, @@ -79,6 +80,7 @@ class VariableLaRCConstants(_LaRCConstantsMixin, _VariablePropertySet): """Variable LaRC failure criterion properties.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" fracture_angle_under_compression = variable_material_grpc_data_property( "fracture_angle_under_compression" diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/puck_constants.py b/src/ansys/acp/core/_tree_objects/material/property_sets/puck_constants.py index a604c44f0c..024240c5c8 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/puck_constants.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/puck_constants.py @@ -60,6 +60,7 @@ class ConstantPuckConstants(_PuckConstantsMixin, _ConstantPropertySet): """Constant Puck constants material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" def __init__( self, @@ -125,6 +126,7 @@ class VariablePuckConstants(_PuckConstantsMixin, _VariablePropertySet): """Variable Puck constants material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" p_21_pos = variable_material_grpc_data_property("p_21_pos") p_21_neg = variable_material_grpc_data_property("p_21_neg") diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/strain_limits.py b/src/ansys/acp/core/_tree_objects/material/property_sets/strain_limits.py index b40833b505..e00a3b4fa1 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/strain_limits.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/strain_limits.py @@ -70,6 +70,7 @@ class ConstantStrainLimits(_StrainLimitsMixin, _ConstantPropertySet): """Constant strain limits material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" @classmethod def from_isotropic_constants( @@ -172,6 +173,7 @@ class VariableStrainLimits(_StrainLimitsMixin, _VariablePropertySet): """Variable strain limits material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" effective_strain = variable_material_grpc_data_property("effective_strain", **_ISOTROPIC_KWARGS) eXc = variable_material_grpc_data_property("eXc", **_ORTHOTROPIC_KWARGS) diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/stress_limits.py b/src/ansys/acp/core/_tree_objects/material/property_sets/stress_limits.py index 284c1fbd15..2c895f4cb8 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/stress_limits.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/stress_limits.py @@ -70,6 +70,7 @@ class ConstantStressLimits(_StressLimitsMixin, _ConstantPropertySet): """Constant stress limits material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" @classmethod def from_isotropic_constants( @@ -172,6 +173,7 @@ class VariableStressLimits(_StressLimitsMixin, _VariablePropertySet): """Variable stress limits material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" effective_stress = variable_material_grpc_data_property("effective_stress", **_ISOTROPIC_KWARGS) Xc = variable_material_grpc_data_property("Xc", **_ORTHOTROPIC_KWARGS) diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/tsai_wu_constants.py b/src/ansys/acp/core/_tree_objects/material/property_sets/tsai_wu_constants.py index e0c0c590dd..b6439a159e 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/tsai_wu_constants.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/tsai_wu_constants.py @@ -44,6 +44,7 @@ class ConstantTsaiWuConstants(_TsaiWuConstantsMixin, _ConstantPropertySet): """Constant Tsai-Wu constants material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" def __init__( self, @@ -71,6 +72,7 @@ class VariableTsaiWuConstants(_TsaiWuConstantsMixin, _VariablePropertySet): """Variable Tsai-Wu constants material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" XY = variable_material_grpc_data_property("XY") XZ = variable_material_grpc_data_property("XZ") diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/woven_characterization.py b/src/ansys/acp/core/_tree_objects/material/property_sets/woven_characterization.py index 98164ae487..d2262e9713 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/woven_characterization.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/woven_characterization.py @@ -44,6 +44,7 @@ class ConstantWovenCharacterization(_WovenCharacterizationMixin, _ConstantProper """Constant woven characterization material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" def __init__( self, @@ -98,6 +99,7 @@ class VariableWovenCharacterization(_WovenCharacterizationMixin, _VariableProper """Variable woven characterization material property set.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" E1_1 = variable_material_grpc_data_property("E1_1") E2_1 = variable_material_grpc_data_property("E2_1") diff --git a/src/ansys/acp/core/_tree_objects/material/property_sets/woven_stress_limits.py b/src/ansys/acp/core/_tree_objects/material/property_sets/woven_stress_limits.py index eea7145dc3..b3f10a344f 100644 --- a/src/ansys/acp/core/_tree_objects/material/property_sets/woven_stress_limits.py +++ b/src/ansys/acp/core/_tree_objects/material/property_sets/woven_stress_limits.py @@ -44,6 +44,7 @@ class ConstantWovenStressLimits(_WovenStressLimitsMixin, _ConstantPropertySet): """Constant stress limits property set for woven materials.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" def __init__( self, @@ -89,6 +90,7 @@ class VariableWovenStressLimits(_WovenStressLimitsMixin, _VariablePropertySet): """Variable stress limits property set for woven materials.""" _GRPC_PROPERTIES = tuple() + _SUPPORTED_SINCE = "24.2" Xc = variable_material_grpc_data_property("Xc") Yc = variable_material_grpc_data_property("Yc") diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 3efca801ba..3a7fa90288 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -226,6 +226,7 @@ class Model(TreeObject): _COLLECTION_LABEL = "models" _OBJECT_INFO_TYPE = model_pb2.ObjectInfo + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/modeling_group.py b/src/ansys/acp/core/_tree_objects/modeling_group.py index cfeb18d771..fb375e06cc 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_group.py +++ b/src/ansys/acp/core/_tree_objects/modeling_group.py @@ -71,6 +71,7 @@ class ModelingGroup(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "modeling_groups" _OBJECT_INFO_TYPE = modeling_group_pb2.ObjectInfo _CREATE_REQUEST_TYPE = modeling_group_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__(self, *, name: str = "ModelingGroup"): super().__init__(name=name) diff --git a/src/ansys/acp/core/_tree_objects/modeling_ply.py b/src/ansys/acp/core/_tree_objects/modeling_ply.py index 5b2b201d69..36e174d451 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/modeling_ply.py @@ -131,6 +131,8 @@ class TaperEdge(GenericEdgePropertyType): offset is ``-offset/tan(angle)``. """ + _SUPPORTED_SINCE = "24.2" + def __init__(self, edge_set: EdgeSet, *, angle: float, offset: float): self._callback_apply_changes: Callable[[], None] | None = None self.edge_set = edge_set @@ -302,6 +304,7 @@ class ModelingPly(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "modeling_plies" _OBJECT_INFO_TYPE = modeling_ply_pb2.ObjectInfo _CREATE_REQUEST_TYPE = modeling_ply_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py index 613e60c355..064b165f84 100644 --- a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py +++ b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py @@ -160,6 +160,7 @@ class OrientedSelectionSet(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "oriented_selection_sets" _OBJECT_INFO_TYPE = oriented_selection_set_pb2.ObjectInfo _CREATE_REQUEST_TYPE = oriented_selection_set_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py b/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py index a6c28cd42a..c9bcfac5e4 100644 --- a/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py @@ -105,6 +105,7 @@ class ParallelSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "parallel_selection_rules" _OBJECT_INFO_TYPE = parallel_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = parallel_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/production_ply.py b/src/ansys/acp/core/_tree_objects/production_ply.py index ff9d7b2eb5..eb4cd52123 100644 --- a/src/ansys/acp/core/_tree_objects/production_ply.py +++ b/src/ansys/acp/core/_tree_objects/production_ply.py @@ -104,6 +104,7 @@ class ProductionPly(ReadOnlyTreeObject, IdTreeObject): _COLLECTION_LABEL = "production_plies" _OBJECT_INFO_TYPE = production_ply_pb2.ObjectInfo + _SUPPORTED_SINCE = "24.2" def _create_stub(self) -> production_ply_pb2_grpc.ObjectServiceStub: return production_ply_pb2_grpc.ObjectServiceStub(self._channel) diff --git a/src/ansys/acp/core/_tree_objects/rosette.py b/src/ansys/acp/core/_tree_objects/rosette.py index 37c179d99a..bf51eddfe5 100644 --- a/src/ansys/acp/core/_tree_objects/rosette.py +++ b/src/ansys/acp/core/_tree_objects/rosette.py @@ -68,6 +68,7 @@ class Rosette(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "rosettes" _OBJECT_INFO_TYPE = rosette_pb2.ObjectInfo _CREATE_REQUEST_TYPE = rosette_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/sensor.py b/src/ansys/acp/core/_tree_objects/sensor.py index 8c47d03e3f..0f1e2709c1 100644 --- a/src/ansys/acp/core/_tree_objects/sensor.py +++ b/src/ansys/acp/core/_tree_objects/sensor.py @@ -76,6 +76,7 @@ class Sensor(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "sensors" _OBJECT_INFO_TYPE = sensor_pb2.ObjectInfo _CREATE_REQUEST_TYPE = sensor_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py b/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py index 433c8c1ae4..7fbb45702b 100644 --- a/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py @@ -101,6 +101,7 @@ class SphericalSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "spherical_selection_rules" _OBJECT_INFO_TYPE = spherical_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = spherical_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/stackup.py b/src/ansys/acp/core/_tree_objects/stackup.py index f12ff5d762..8f69713553 100644 --- a/src/ansys/acp/core/_tree_objects/stackup.py +++ b/src/ansys/acp/core/_tree_objects/stackup.py @@ -78,6 +78,8 @@ class FabricWithAngle(GenericEdgePropertyType): """ + _SUPPORTED_SINCE = "24.2" + def __init__(self, fabric: Fabric, angle: float = 0.0): self._callback_apply_changes: Callable[[], None] | None = None self.fabric = fabric @@ -185,6 +187,7 @@ class Stackup(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "stackups" _OBJECT_INFO_TYPE = stackup_pb2.ObjectInfo _CREATE_REQUEST_TYPE = stackup_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/sublaminate.py b/src/ansys/acp/core/_tree_objects/sublaminate.py index f3e7f03ad1..ea3958d910 100644 --- a/src/ansys/acp/core/_tree_objects/sublaminate.py +++ b/src/ansys/acp/core/_tree_objects/sublaminate.py @@ -68,6 +68,8 @@ class Lamina(GenericEdgePropertyType): """ + _SUPPORTED_SINCE = "24.2" + def __init__(self, material: _LINKABLE_MATERIAL_TYPES, angle: float = 0.0): self._callback_apply_changes: Callable[[], None] | None = None self.material = material @@ -174,6 +176,7 @@ class SubLaminate(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "sublaminates" _OBJECT_INFO_TYPE = sublaminate_pb2.ObjectInfo _CREATE_REQUEST_TYPE = sublaminate_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/tube_selection_rule.py b/src/ansys/acp/core/_tree_objects/tube_selection_rule.py index 7920289228..6c3f29c63b 100644 --- a/src/ansys/acp/core/_tree_objects/tube_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/tube_selection_rule.py @@ -109,6 +109,7 @@ class TubeSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "tube_selection_rules" _OBJECT_INFO_TYPE = tube_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = tube_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py b/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py index 4a0be106b8..b70c8934ac 100644 --- a/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py @@ -116,6 +116,7 @@ class VariableOffsetSelectionRule(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "variable_offset_selection_rules" _OBJECT_INFO_TYPE = variable_offset_selection_rule_pb2.ObjectInfo _CREATE_REQUEST_TYPE = variable_offset_selection_rule_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, diff --git a/src/ansys/acp/core/_tree_objects/virtual_geometry.py b/src/ansys/acp/core/_tree_objects/virtual_geometry.py index 0af3696054..f7ab2dcb7a 100644 --- a/src/ansys/acp/core/_tree_objects/virtual_geometry.py +++ b/src/ansys/acp/core/_tree_objects/virtual_geometry.py @@ -53,6 +53,8 @@ class SubShape(GenericEdgePropertyType): """Represents a sub-shape of a virtual geometry.""" + _SUPPORTED_SINCE = "24.2" + def __init__(self, cad_geometry: CADGeometry, path: str): self._callback_apply_changes: Callable[[], None] | None = None self.cad_geometry = cad_geometry @@ -150,6 +152,7 @@ class VirtualGeometry(CreatableTreeObject, IdTreeObject): _COLLECTION_LABEL = "virtual_geometries" _OBJECT_INFO_TYPE = virtual_geometry_pb2.ObjectInfo _CREATE_REQUEST_TYPE = virtual_geometry_pb2.CreateRequest + _SUPPORTED_SINCE = "24.2" def __init__( self, From 0bc1c835592dcfdf14c868749ef1c52437b2e186 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:57:39 +0000 Subject: [PATCH 18/96] Bump the dependencies group with 3 updates (#621) Bumps the dependencies group with 3 updates: [ansys-mechanical-core](https://github.com/ansys/pymechanical), [ansys-sphinx-theme](https://github.com/ansys/ansys-sphinx-theme) and [hypothesis](https://github.com/HypothesisWorks/hypothesis). Updates `ansys-mechanical-core` from 0.11.7 to 0.11.8 - [Release notes](https://github.com/ansys/pymechanical/releases) - [Changelog](https://github.com/ansys/pymechanical/blob/main/CHANGELOG.md) - [Commits](https://github.com/ansys/pymechanical/compare/v0.11.7...v0.11.8) Updates `ansys-sphinx-theme` from 1.1.5 to 1.1.6 - [Release notes](https://github.com/ansys/ansys-sphinx-theme/releases) - [Commits](https://github.com/ansys/ansys-sphinx-theme/compare/v1.1.5...v1.1.6) Updates `hypothesis` from 6.115.2 to 6.115.3 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.115.2...hypothesis-python-6.115.3) --- updated-dependencies: - dependency-name: ansys-mechanical-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: ansys-sphinx-theme dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index d4f8fe40e6..ec7ef319e4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -392,18 +392,18 @@ tests = ["ansys-mapdl-core (==0.68.1)", "numpy (==1.26.4)", "pyansys-tools-repor [[package]] name = "ansys-mechanical-core" -version = "0.11.7" +version = "0.11.8" description = "A python wrapper for Ansys Mechanical" optional = false -python-versions = "<4.0,>=3.9" +python-versions = "<4.0,>=3.10" files = [ - {file = "ansys_mechanical_core-0.11.7-py3-none-any.whl", hash = "sha256:e13a98a87648ea80ad3c9d3901504d05c3500b359b0f88dfb06bc066e97783f1"}, - {file = "ansys_mechanical_core-0.11.7.tar.gz", hash = "sha256:6c54a5b4504ff3ffa3823e1c8e53c91b6d2f182c6fe4f6fd553f68a3e882f940"}, + {file = "ansys_mechanical_core-0.11.8-py3-none-any.whl", hash = "sha256:fc9cbce588f6ff9d8c877f4c83d59b90cbf36df75d8c994320bffd2d335a3cff"}, + {file = "ansys_mechanical_core-0.11.8.tar.gz", hash = "sha256:607f3d3fb89c13594d81be6692196727f723005aff112f51c4b26a233d3a8277"}, ] [package.dependencies] ansys-api-mechanical = "0.1.2" -ansys-mechanical-env = "0.1.7" +ansys-mechanical-env = "0.1.8" ansys-platform-instancemanagement = ">=1.0.1" ansys-pythonnet = ">=3.1.0rc2" ansys-tools-path = ">=0.3.1" @@ -412,22 +412,23 @@ click = ">=8.1.3" clr-loader = "0.2.6" grpcio = ">=1.30.0" protobuf = ">=3.12.2,<6" +psutil = "6.0.0" tqdm = ">=4.45.0" [package.extras] -doc = ["ansys-sphinx-theme[autoapi] (==1.0.7)", "grpcio (==1.66.0)", "imageio (==2.35.1)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.0)", "numpydoc (==1.8.0)", "pandas (==2.2.2)", "panel (==1.4.5)", "plotly (==5.23.0)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.0.2)", "sphinx-autobuild (==2024.4.16)", "sphinx-autodoc-typehints (==2.2.3)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.17.1)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] -tests = ["pytest (==8.3.2)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.0)"] +doc = ["ansys-sphinx-theme[autoapi] (==1.1.4)", "grpcio (==1.66.2)", "imageio (==2.36.0)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "panel (==1.5.2)", "plotly (==5.24.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.1.3)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.5.0)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] +tests = ["psutil (==6.0.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.2)"] viz = ["ansys-tools-visualization-interface (>=0.2.6)", "usd-core (==24.8)"] [[package]] name = "ansys-mechanical-env" -version = "0.1.7" +version = "0.1.8" description = "A python wrapper for loading environment variables when using PyMechanical embedded instances in Linux." optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" files = [ - {file = "ansys_mechanical_env-0.1.7-py3-none-any.whl", hash = "sha256:03004cc200a9c8e43fdc04b8879436f8089d4391daf98d7fac429bc997b62d6d"}, - {file = "ansys_mechanical_env-0.1.7.tar.gz", hash = "sha256:29a4ce796cf3719828f6af4fa7d1097333cb12d1b10b6ab199efe0f7ec8fc346"}, + {file = "ansys_mechanical_env-0.1.8-py3-none-any.whl", hash = "sha256:4746c83924cba91137ffd367712a033abf2a8ccc66e7e02b76c1a3a6a80b4bf9"}, + {file = "ansys_mechanical_env-0.1.8.tar.gz", hash = "sha256:5286202292f6aeb3bf2278003059dbcd64774db2decb16a8cf68d15affef51d6"}, ] [package.dependencies] @@ -470,13 +471,13 @@ clr-loader = ">=0.2.6,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.1.5" +version = "1.1.6" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.10" files = [ - {file = "ansys_sphinx_theme-1.1.5-py3-none-any.whl", hash = "sha256:43e24af005b9ff84f83279839f24fe61faa5fe3d882b01134366043e7ad08315"}, - {file = "ansys_sphinx_theme-1.1.5.tar.gz", hash = "sha256:c1e36732f424597945ae45583c92fe69dcca0ecb67300e5dc95a48c63052baa4"}, + {file = "ansys_sphinx_theme-1.1.6-py3-none-any.whl", hash = "sha256:356593038adc5f407ba9ed862eda41babc99fe88063c6a990e3b4cc32b9e7a06"}, + {file = "ansys_sphinx_theme-1.1.6.tar.gz", hash = "sha256:a1c7afcfab172d7cd3b59b84d6c4ae6d703920ae5f682542193dd95e6fa3d81c"}, ] [package.dependencies] @@ -1846,13 +1847,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.115.2" +version = "6.115.3" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.115.2-py3-none-any.whl", hash = "sha256:d0ccbd67f588a6abcdc17a4f06fea3b2bc0a166ecd89126fb90985036c1940fe"}, - {file = "hypothesis-6.115.2.tar.gz", hash = "sha256:6df95ea6cccf950f80e142f43a684d1462e26c5e28ba29ab4eceb8399e207e0b"}, + {file = "hypothesis-6.115.3-py3-none-any.whl", hash = "sha256:d2770b0db08ad666fe6ff36027910039ab681084d13bcf9c057449c2e27099c4"}, + {file = "hypothesis-6.115.3.tar.gz", hash = "sha256:d4efc8c7371bd4ec906d2777f1f18fee5539e47b3d7c7cdc93d1026ad35d9b33"}, ] [package.dependencies] From 86f5d32bc519f11b0a5bd6a422a1a6429a0fc7ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:32:13 +0200 Subject: [PATCH 19/96] Bump mypy from 1.12.0 to 1.12.1 in the dependencies group (#622) Bumps the dependencies group with 1 update: [mypy](https://github.com/python/mypy). Updates `mypy` from 1.12.0 to 1.12.1 - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.12.0...v1.12.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 66 ++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/poetry.lock b/poetry.lock index ec7ef319e4..c18377f615 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2796,43 +2796,43 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.12.0" +version = "1.12.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4397081e620dc4dc18e2f124d5e1d2c288194c2c08df6bdb1db31c38cd1fe1ed"}, - {file = "mypy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:684a9c508a283f324804fea3f0effeb7858eb03f85c4402a967d187f64562469"}, - {file = "mypy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cabe4cda2fa5eca7ac94854c6c37039324baaa428ecbf4de4567279e9810f9e"}, - {file = "mypy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:060a07b10e999ac9e7fa249ce2bdcfa9183ca2b70756f3bce9df7a92f78a3c0a"}, - {file = "mypy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0eff042d7257f39ba4ca06641d110ca7d2ad98c9c1fb52200fe6b1c865d360ff"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b86de37a0da945f6d48cf110d5206c5ed514b1ca2614d7ad652d4bf099c7de7"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c7c5ce0c1be0b0aea628374e6cf68b420bcc772d85c3c974f675b88e3e6e57"}, - {file = "mypy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a64ee25f05fc2d3d8474985c58042b6759100a475f8237da1f4faf7fcd7e6309"}, - {file = "mypy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faca7ab947c9f457a08dcb8d9a8664fd438080e002b0fa3e41b0535335edcf7f"}, - {file = "mypy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:5bc81701d52cc8767005fdd2a08c19980de9ec61a25dbd2a937dfb1338a826f9"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475"}, - {file = "mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9"}, - {file = "mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642"}, - {file = "mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893"}, - {file = "mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721"}, - {file = "mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3"}, - {file = "mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eafc1b7319b40ddabdc3db8d7d48e76cfc65bbeeafaa525a4e0fa6b76175467f"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b9ce1ad8daeb049c0b55fdb753d7414260bad8952645367e70ac91aec90e07e"}, - {file = "mypy-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfe012b50e1491d439172c43ccb50db66d23fab714d500b57ed52526a1020bb7"}, - {file = "mypy-1.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c40658d4fa1ab27cb53d9e2f1066345596af2f8fe4827defc398a09c7c9519b"}, - {file = "mypy-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:dee78a8b9746c30c1e617ccb1307b351ded57f0de0d287ca6276378d770006c0"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b5df6c8a8224f6b86746bda716bbe4dbe0ce89fd67b1fa4661e11bfe38e8ec8"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5feee5c74eb9749e91b77f60b30771563327329e29218d95bedbe1257e2fe4b0"}, - {file = "mypy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77278e8c6ffe2abfba6db4125de55f1024de9a323be13d20e4f73b8ed3402bd1"}, - {file = "mypy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dcfb754dea911039ac12434d1950d69a2f05acd4d56f7935ed402be09fad145e"}, - {file = "mypy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:06de0498798527451ffb60f68db0d368bd2bae2bbfb5237eae616d4330cc87aa"}, - {file = "mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266"}, - {file = "mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d"}, + {file = "mypy-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801"}, + {file = "mypy-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5"}, + {file = "mypy-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1"}, + {file = "mypy-1.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627"}, + {file = "mypy-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20"}, + {file = "mypy-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735"}, + {file = "mypy-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66"}, + {file = "mypy-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6"}, + {file = "mypy-1.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931"}, + {file = "mypy-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811"}, + {file = "mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f"}, + {file = "mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0"}, + {file = "mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042"}, + {file = "mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179"}, + {file = "mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a"}, + {file = "mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc"}, + {file = "mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635"}, + {file = "mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81"}, + {file = "mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4"}, + {file = "mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02"}, + {file = "mypy-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b16fe09f9c741d85a2e3b14a5257a27a4f4886c171d562bc5a5e90d8591906b8"}, + {file = "mypy-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0dcc1e843d58f444fce19da4cce5bd35c282d4bde232acdeca8279523087088a"}, + {file = "mypy-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10ba7de5c616e44ad21005fa13450cd0de7caaa303a626147d45307492e4f2d"}, + {file = "mypy-1.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e6fe449223fa59fbee351db32283838a8fee8059e0028e9e6494a03802b4004"}, + {file = "mypy-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc6e2a2195a290a7fd5bac3e60b586d77fc88e986eba7feced8b778c373f9afe"}, + {file = "mypy-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:de5b2a8988b4e1269a98beaf0e7cc71b510d050dce80c343b53b4955fff45f19"}, + {file = "mypy-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843826966f1d65925e8b50d2b483065c51fc16dc5d72647e0236aae51dc8d77e"}, + {file = "mypy-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe20f89da41a95e14c34b1ddb09c80262edcc295ad891f22cc4b60013e8f78d"}, + {file = "mypy-1.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8135ffec02121a75f75dc97c81af7c14aa4ae0dda277132cfcd6abcd21551bfd"}, + {file = "mypy-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a7b76fa83260824300cc4834a3ab93180db19876bce59af921467fd03e692810"}, + {file = "mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e"}, + {file = "mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d"}, ] [package.dependencies] From fbbd5ff104524f9ee4f349209f03fc8473946d96 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 23 Oct 2024 12:00:39 +0200 Subject: [PATCH 20/96] Add regression test for cloning / storing locked element sets (#624) Add a regression test to check that #565 is fixed in the backend. Closes #565. --- tests/conftest.py | 11 +++++++++++ tests/unittests/test_element_set.py | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index d6b22b2828..c41468fa7b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -276,3 +276,14 @@ def inner(version: str): yield return inner + + +@pytest.fixture +def skip_before_version(acp_instance): + """Skip a test before a certain server version.""" + + def inner(version: str): + if parse_version(acp_instance.server_version) < parse_version(version): + pytest.skip(f"Test is not supported before version {version}") + + return inner diff --git a/tests/unittests/test_element_set.py b/tests/unittests/test_element_set.py index b016caba55..4ce5308a63 100644 --- a/tests/unittests/test_element_set.py +++ b/tests/unittests/test_element_set.py @@ -66,3 +66,21 @@ def default_properties(): CREATE_METHOD_NAME = "create_element_set" INITIAL_OBJECT_NAMES = ("All_Elements",) + + +def test_clone_locked(parent_object, skip_before_version): + """Test that a locked element set can be correctly cloned. + + Regression test for #565: cloning and storing a locked element + set produces an empty element set. + The root cause for this issue was that the locked element set + did not expose their 'element_labels' in the API.ga + """ + skip_before_version("25.1") + + element_set = parent_object.element_sets["All_Elements"] + assert len(element_set.element_labels) > 0 + cloned_element_set = element_set.clone() + cloned_element_set.store(parent=parent_object) + assert not cloned_element_set.locked + assert len(cloned_element_set.element_labels) > 0 From 1f82dada9c735f4736577575d3ac9c17c56b1b29 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 23 Oct 2024 12:20:09 +0200 Subject: [PATCH 21/96] Add interface layer exposure (#609) Add exposure for the `InterfaceLayer` object type. --- doc/source/api/tree_objects.rst | 1 + doc/source/index.rst | 1 - src/ansys/acp/core/__init__.py | 2 + src/ansys/acp/core/_tree_objects/__init__.py | 2 + .../acp/core/_tree_objects/interface_layer.py | 129 ++++++++++++++++++ .../acp/core/_tree_objects/modeling_group.py | 17 ++- tests/unittests/test_interface_layer.py | 84 ++++++++++++ 7 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/interface_layer.py create mode 100644 tests/unittests/test_interface_layer.py diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 64c0660fbb..861e42be11 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -16,6 +16,7 @@ ACP objects ElementSet Fabric GeometricalSelectionRule + InterfaceLayer LookUpTable1D LookUpTable1DColumn LookUpTable3D diff --git a/doc/source/index.rst b/doc/source/index.rst index 2ed66f2fad..3cfe23eaf0 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -78,5 +78,4 @@ Limitations * Only shell workflows are supported, solid models can not yet be defined using PyACP * FieldDefinitions for variable material properties are not supported -* Butt joint sequences and interface layers are not supported * Section cuts are not supported diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index c254523b1b..8e3566465b 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -76,6 +76,7 @@ GeometricalSelectionRuleElementalData, GeometricalSelectionRuleNodalData, IgnorableEntity, + InterfaceLayer, Lamina, LinkedSelectionRule, LookUpTable1D, @@ -189,6 +190,7 @@ "get_dpf_unit_system", "get_model_tree", "IgnorableEntity", + "InterfaceLayer", "Lamina", "launch_acp", "LaunchMode", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 852459051f..2c7b24b34b 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -76,6 +76,7 @@ GeometricalSelectionRuleElementalData, GeometricalSelectionRuleNodalData, ) +from .interface_layer import InterfaceLayer from .linked_selection_rule import LinkedSelectionRule from .lookup_table_1d import LookUpTable1D from .lookup_table_1d_column import LookUpTable1DColumn @@ -155,6 +156,7 @@ "GeometricalSelectionRuleElementalData", "GeometricalSelectionRuleNodalData", "IgnorableEntity", + "InterfaceLayer", "InterpolationOptions", "Lamina", "LinkedSelectionRule", diff --git a/src/ansys/acp/core/_tree_objects/interface_layer.py b/src/ansys/acp/core/_tree_objects/interface_layer.py new file mode 100644 index 0000000000..7b55da94d4 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/interface_layer.py @@ -0,0 +1,129 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable +import dataclasses + +from ansys.api.acp.v0 import interface_layer_pb2, interface_layer_pb2_grpc + +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.linked_object_list import ( + define_linked_object_list, + define_polymorphic_linked_object_list, +) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + mark_grpc_properties, +) +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) +from .base import CreatableTreeObject, IdTreeObject +from .element_set import ElementSet +from .enums import status_type_from_pb +from .object_registry import register +from .oriented_selection_set import OrientedSelectionSet + + +@dataclasses.dataclass +class InterfaceLayerElementalData(ElementalData): + """Represents elemental data for a Modeling Ply.""" + + normal: VectorData | None = None + + +@dataclasses.dataclass +class InterfaceLayerNodalData(NodalData): + """Represents nodal data for a Modeling Ply.""" + + ply_offset: VectorData | None = None + + +@mark_grpc_properties +@register +class InterfaceLayer(CreatableTreeObject, IdTreeObject): + """Instantiate an interface layer. + + The interface layer is a separation layer in the stacking sequence. It can be + used to analyze the crack growth of existing cracks. They can also be used to + define contacts zones between two layers. + The topology is defined with an interface layer in ACP, while all other fracture + settings need to be specified in the downstream analysis (MAPDL or Mechanical). + + Parameters + ---------- + name : + Name of the interface layer. + global_ply_nr : + Global ply number for the stacking sequence. + active : + Inactive interface layers are ignored in ACP and the downstream analysis. + oriented_selection_sets : + Oriented Selection Set for the expansion of the interface layer. + open_area_sets : + Defines the initial crack of a Virtual Crack Closure Technique (VCCT) layer. + Can contain ``OrientedSelectionSet`` and ``ElementSet`` objects. + """ + + __slots__: Iterable[str] = tuple() + _COLLECTION_LABEL = "interface_layers" + _OBJECT_INFO_TYPE = interface_layer_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = interface_layer_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "InterfaceLayer", + global_ply_nr: int = 0, + active: bool = True, + oriented_selection_sets: Iterable[OrientedSelectionSet] = (), + open_area_sets: Iterable[ElementSet | OrientedSelectionSet] = (), + ): + super().__init__(name=name) + self.global_ply_nr = global_ply_nr + self.active = active + self.oriented_selection_sets = oriented_selection_sets + self.open_area_sets = open_area_sets + + def _create_stub(self) -> interface_layer_pb2_grpc.ObjectServiceStub: + return interface_layer_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + global_ply_nr: ReadWriteProperty[int, int] = grpc_data_property("properties.global_ply_nr") + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + oriented_selection_sets = define_linked_object_list( + "properties.oriented_selection_sets", OrientedSelectionSet + ) + open_area_sets = define_polymorphic_linked_object_list( + "properties.open_area_sets", allowed_types=(ElementSet, OrientedSelectionSet) + ) + + elemental_data = elemental_data_property(InterfaceLayerElementalData) + nodal_data = nodal_data_property(InterfaceLayerNodalData) diff --git a/src/ansys/acp/core/_tree_objects/modeling_group.py b/src/ansys/acp/core/_tree_objects/modeling_group.py index fb375e06cc..bb0058b669 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_group.py +++ b/src/ansys/acp/core/_tree_objects/modeling_group.py @@ -25,7 +25,12 @@ from collections.abc import Iterable import dataclasses -from ansys.api.acp.v0 import modeling_group_pb2, modeling_group_pb2_grpc, modeling_ply_pb2_grpc +from ansys.api.acp.v0 import ( + interface_layer_pb2_grpc, + modeling_group_pb2, + modeling_group_pb2_grpc, + modeling_ply_pb2_grpc, +) from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping from ._grpc_helpers.property_helper import mark_grpc_properties @@ -37,6 +42,7 @@ nodal_data_property, ) from .base import CreatableTreeObject, IdTreeObject +from .interface_layer import InterfaceLayer from .modeling_ply import ModelingPly from .object_registry import register @@ -86,6 +92,15 @@ def _create_stub(self) -> modeling_group_pb2_grpc.ObjectServiceStub: module_name=__module__, ) modeling_plies = define_mutable_mapping(ModelingPly, modeling_ply_pb2_grpc.ObjectServiceStub) + interface_layers = define_mutable_mapping( + InterfaceLayer, interface_layer_pb2_grpc.ObjectServiceStub + ) + create_interface_layer = define_create_method( + InterfaceLayer, + func_name="create_interface_layer", + parent_class_name="ModelingGroup", + module_name=__module__, + ) elemental_data = elemental_data_property(ModelingGroupElementalData) nodal_data = nodal_data_property(ModelingGroupNodalData) diff --git a/tests/unittests/test_interface_layer.py b/tests/unittests/test_interface_layer.py new file mode 100644 index 0000000000..8dd618e993 --- /dev/null +++ b/tests/unittests/test_interface_layer.py @@ -0,0 +1,84 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import InterfaceLayer + +from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(InterfaceLayer._SUPPORTED_SINCE): + pytest.skip("InterfaceLayer is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def parent_object(model): + return list(model.modeling_groups.values())[0] + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_interface_layer() + + +class TestInterfaceLayer(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "interface_layers" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "active": True, + "oriented_selection_sets": [], + "open_area_sets": [], + } + + CREATE_METHOD_NAME = "create_interface_layer" + + @staticmethod + @pytest.fixture + def object_properties(model): + oriented_selection_sets = [model.create_oriented_selection_set() for _ in range(3)] + open_area_sets = [model.create_oriented_selection_set(), model.create_element_set()] + return ObjectPropertiesToTest( + read_write=[ + ("name", "Interface layer name"), + ("oriented_selection_sets", oriented_selection_sets), + ("open_area_sets", open_area_sets), + ("active", False), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) From 6c097828de081b1fe5a9e75f409d8dd5c895c828 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 23 Oct 2024 13:20:09 +0200 Subject: [PATCH 22/96] Add testing for 2025R1 server (#626) Add testing for the 2025R1 server, using a test matrix. Since the `env.DOCKER_IMAGE_NAME` cannot be directly used in the matrix, the `docker_image_suffix` variable now only affects the doc + other jobs. Closes #625. --- .github/workflows/ci_cd.yml | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 048c42ed7f..7f8e098b9e 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -1,4 +1,3 @@ -# check spelling, codestyle name: GitHub CI # run only on main branch. This avoids duplicated actions on PRs @@ -114,6 +113,12 @@ jobs: strategy: matrix: python-version: ["3.10", "3.11", "3.12"] + server-version: ["latest"] + include: + - python-version: "3.12" + server-version: "2024R2" + - python-version: "3.12" + server-version: "2025R1" steps: - uses: actions/checkout@v4 @@ -170,7 +175,7 @@ jobs: poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html env: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} - IMAGE_NAME: ${{ env.DOCKER_IMAGE_NAME }} + IMAGE_NAME: ghcr.io/ansys/acp:${{ matrix.server-version }} - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v4 @@ -178,34 +183,15 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: coverage.xml - flags: 'server-latest,python-${{ matrix.python-version }}' - - - name: "Unit testing (2024R2 server)" - if: matrix.python-version == env.MAIN_PYTHON_VERSION - working-directory: tests/unittests - run: | - docker pull $IMAGE_NAME - poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html - env: - LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} - IMAGE_NAME: "ghcr.io/ansys/acp:2024r2" - - - name: "Upload coverage to Codecov (2024R2 server)" - if: matrix.python-version == env.MAIN_PYTHON_VERSION - uses: codecov/codecov-action@v4 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - files: coverage.xml - flags: 'server-242,python-${{ matrix.python-version }}' + flags: 'server-${{ matrix.server-version }},python-${{ matrix.python-version }}' - name: Benchmarks working-directory: tests/benchmarks run: | - poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --build-benchmark-image --benchmark-json benchmark_output.json --benchmark-group-by=fullname ${{ (matrix.python-version == '3.10' && github.ref == 'refs/heads/main') && ' ' || '--validate-benchmarks-only' }} + poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --build-benchmark-image --benchmark-json benchmark_output.json --benchmark-group-by=fullname ${{ (matrix.python-version == env.MAIN_PYTHON_VERSION && matrix.server-version == 'latest' && github.ref == 'refs/heads/main') && ' ' || '--validate-benchmarks-only' }} env: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} - IMAGE_NAME: ${{ env.DOCKER_IMAGE_NAME }} + IMAGE_NAME: ghcr.io/ansys/acp:${{ matrix.server-version }} - name: Store benchmark result uses: benchmark-action/github-action-benchmark@v1 @@ -216,7 +202,7 @@ jobs: benchmark-data-dir-path: benchmarks auto-push: true github-token: ${{ secrets.GITHUB_TOKEN }} - if: matrix.python-version == env.MAIN_PYTHON_VERSION && github.ref == 'refs/heads/main' + if: matrix.python-version == env.MAIN_PYTHON_VERSION && matrix.server-version == 'latest' && github.ref == 'refs/heads/main' doctest: name: Test documentation snippets From d3986e6d68c9c2e303231d097a758c8000883454 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 23 Oct 2024 14:27:03 +0200 Subject: [PATCH 23/96] Enable changing the unit system (#567) Allow setting the `unit_system` attribute on the model. The `UnitSystemType` enum is extended by a value `FROM_FILE = "from_file"`, which is an alias to `UNDEFINED`. This allows for using the more descriptive `"from_file"` when loading an FE model. When getting the `unit_system` attribute from the model, the `FROM_FILE` is never returned; the "primary" value `UNDEFINED` is used instead, as before. --- src/ansys/acp/core/_server/acp_instance.py | 11 ++++-- .../_grpc_helpers/enum_wrapper.py | 24 ++++++++++++- src/ansys/acp/core/_tree_objects/enums.py | 3 ++ src/ansys/acp/core/_tree_objects/model.py | 27 +++++++++++--- tests/unittests/test_model.py | 36 +++++++++++++++++++ tests/unittests/test_workflow.py | 10 ++++-- 6 files changed, 99 insertions(+), 12 deletions(-) diff --git a/src/ansys/acp/core/_server/acp_instance.py b/src/ansys/acp/core/_server/acp_instance.py index e790dd3aa4..868705d052 100644 --- a/src/ansys/acp/core/_server/acp_instance.py +++ b/src/ansys/acp/core/_server/acp_instance.py @@ -161,8 +161,10 @@ def import_model( into ACP composite definitions. Available only when the format is not ``"acp:h5"``. unit_system: - Set the unit system of the model to the given value. Ignored - if the unit system is already set in the FE file. + Defines the unit system of the imported file. Must be set if the + input file does not have units. If the input file does have units, + ``unit_system`` must be either ``"from_file"``, or match the input + unit system. Available only when the format is not ``"acp:h5"``. Returns @@ -180,7 +182,10 @@ def import_model( model = Model._from_file(path=path, server_wrapper=server_wrapper) else: model = Model._from_fe_file( - path=path, server_wrapper=server_wrapper, format=format, **kwargs + path=path, + server_wrapper=server_wrapper, + format=format, + **kwargs, ) if name is not None: model.name = name diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py index 3884d7f80b..ac36bd5af4 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from collections.abc import Callable +from collections.abc import Callable, Mapping +import types from typing import Any __all__ = ["wrap_to_string_enum"] @@ -41,11 +42,29 @@ def wrap_to_string_enum( value_converter: Callable[[str], str] = lambda val: val.lower(), doc: str, explicit_value_list: tuple[int, ...] | None = None, + extra_aliases: Mapping[str, tuple[str, str]] = types.MappingProxyType({}), ) -> tuple[_StrEnumT, Callable[[_StrEnumT], int], Callable[[int], _StrEnumT]]: """Create a string Enum with the same keys as the given protobuf Enum. Values of the enum are the keys, converted to lowercase. + Parameters + ---------- + key_converter : + A callable which converts the protobuf field names to the string enum field names. + value_converter : + A callable which converts the protobuf field names to the string enum values. + doc : + The docstring of the enum. + explicit_value_list : + A list of values that should be included in the enum. If None, all values are included. + extra_aliases : + Allows defining additional fields in the enum which correspond to the same protobuf value. + The keys are the primary enum field values, and the values are tuples of the alias field name + and the alias field value. + Note that the alias will not be used when converting from the protobuf value to the string + enum: the primary field name will be used instead. + Returns ------- : @@ -66,6 +85,9 @@ def wrap_to_string_enum( fields.append((enum_key, enum_value)) to_pb_conversion_dict[enum_value] = pb_value from_pb_conversion_dict[pb_value] = enum_value + for primary_enum_value, (alias_enum_key, alias_enum_value) in extra_aliases.items(): + fields.append((alias_enum_key, alias_enum_value)) + to_pb_conversion_dict[alias_enum_value] = to_pb_conversion_dict[primary_enum_value] res_enum: _StrEnumT = StrEnum(class_name, fields, module=module) # type: ignore res_enum.__doc__ = doc diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index 73331838a0..9b7963fb36 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -210,6 +210,9 @@ unit_system_pb2.UnitSystemType, module=__name__, doc="Available choices for the unit system.", + # When loading from a file, the value 'from_file' is more descriptive than 'undefined', + # so we add an alias for it. + extra_aliases={"undefined": ("FROM_FILE", "from_file")}, ) ( diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 3a7fa90288..b083ecdf68 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -75,10 +75,13 @@ from ._grpc_helpers.exceptions import wrap_grpc_errors from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping from ._grpc_helpers.property_helper import ( + _PROTOBUF_T, + _set_data_attribute, grpc_data_property, grpc_data_property_read_only, mark_grpc_properties, ) +from ._grpc_helpers.protocols import ObjectInfo from ._grpc_helpers.supported_since import supported_since from ._mesh_data import ( ElementalData, @@ -269,8 +272,20 @@ def _create_stub(self) -> model_pb2_grpc.ObjectServiceStub: minimum_analysis_ply_thickness: ReadWriteProperty[float, float] = grpc_data_property( "properties.minimum_analysis_ply_thickness" ) - unit_system = grpc_data_property_read_only( - "properties.unit_system", from_protobuf=unit_system_type_from_pb + + @staticmethod + def _set_unit_system_data_attribute(pb_obj: ObjectInfo, name: str, value: _PROTOBUF_T) -> None: + # remove the 'minimum_analysis_ply_thickness' property from the pb object, to + # allow the backend to convert it to the new unit system. + pb_obj.properties.ClearField("minimum_analysis_ply_thickness") + _set_data_attribute(pb_obj, name, value) + + unit_system = grpc_data_property( + "properties.unit_system", + from_protobuf=unit_system_type_from_pb, + to_protobuf=unit_system_type_to_pb, + setter_func=_set_unit_system_data_attribute, + writable_since="25.1", ) average_element_size: ReadOnlyProperty[float] = grpc_data_property_read_only( @@ -304,7 +319,7 @@ def _from_fe_file( format: FeFormat, # type: ignore ignored_entities: Iterable[IgnorableEntity] = (), # type: ignore convert_section_data: bool = False, - unit_system: UnitSystemType = UnitSystemType.UNDEFINED, + unit_system: UnitSystemType = UnitSystemType.FROM_FILE, ) -> Model: """Load the model from an FE file. @@ -326,8 +341,10 @@ def _from_fe_file( Whether to import the section data of a shell model and convert it into ACP composite definitions. unit_system: - Set the unit system of the model to the given value. Ignored - if the unit system is already set in the FE file. + Defines the unit system of the imported file. Must be set if the + input file does not have units. If the input file does have units, + ``unit_system`` must be either ``"from_file"``, or match the input + unit system. """ format_pb = fe_format_to_pb(format) ignored_entities_pb = [ignorable_entity_to_pb(val) for val in ignored_entities] diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 70ee7143e0..dbb469ba9c 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -278,3 +278,39 @@ def test_parent_access_raises(minimal_complete_model): with pytest.raises(RuntimeError) as exc: minimal_complete_model.parent assert "parent" in str(exc.value) + + +@pytest.mark.parametrize("unit_system", UnitSystemType) +def test_change_unit_system(minimal_complete_model, unit_system, raises_before_version): + assert minimal_complete_model.unit_system == UnitSystemType.MPA + + initial_node_coords = minimal_complete_model.mesh.node_coordinates + initial_minimum_analysis_ply_thickness = minimal_complete_model.minimum_analysis_ply_thickness + + conversion_factor_by_us = { + "mpa": 1.0, + "mks": 1e-3, + "cgs": 0.1, + "si": 1e-3, + "bin": 0.03937008, + "bft": 0.00328084, + "umks": 1e3, + } + + with raises_before_version("25.1"): + if unit_system in (UnitSystemType.UNDEFINED, UnitSystemType.FROM_FILE): + with pytest.raises(ValueError): + minimal_complete_model.unit_system = unit_system + else: + minimal_complete_model.unit_system = unit_system + assert minimal_complete_model.unit_system == unit_system + minimal_complete_model.update() + + np.testing.assert_allclose( + minimal_complete_model.mesh.node_coordinates, + initial_node_coords * conversion_factor_by_us[unit_system.value], + ) + np.testing.assert_allclose( + minimal_complete_model.minimum_analysis_ply_thickness, + initial_minimum_analysis_ply_thickness * conversion_factor_by_us[unit_system.value], + ) diff --git a/tests/unittests/test_workflow.py b/tests/unittests/test_workflow.py index 1654a6d98f..ce237e8d5f 100644 --- a/tests/unittests/test_workflow.py +++ b/tests/unittests/test_workflow.py @@ -107,9 +107,13 @@ def test_workflow_unit_system_dat(acp_instance, model_data_dir, unit_system): # input file does contain the unit system. # Since 25.1, we also allow it if the unit systems match. if parse_version(acp_instance.server_version) < parse_version("25.1"): - allowed_unit_system_values = [UnitSystemType.UNDEFINED] + allowed_unit_system_values = [UnitSystemType.UNDEFINED, UnitSystemType.FROM_FILE] else: - allowed_unit_system_values = [UnitSystemType.UNDEFINED, UnitSystemType.MKS] + allowed_unit_system_values = [ + UnitSystemType.UNDEFINED, + UnitSystemType.FROM_FILE, + UnitSystemType.MKS, + ] if unit_system not in allowed_unit_system_values: with pytest.raises(ValueError) as ex: @@ -134,7 +138,7 @@ def test_workflow_unit_system_cdb(acp_instance, model_data_dir, unit_system): input_file_path = model_data_dir / "minimal_model_2.cdb" - if unit_system == UnitSystemType.UNDEFINED: + if unit_system in (UnitSystemType.UNDEFINED, UnitSystemType.FROM_FILE): with pytest.raises(ValueError) as ex: # Initializing a workflow with an undefined unit system is not allowed # if the input file does not contain the unit system. From 0dcd83498d21b634d3a4add1c3b8a25f62a3701d Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 23 Oct 2024 14:41:27 +0200 Subject: [PATCH 24/96] Add ButtJointSequence exposure (#606) Add the `ButtJointSequence` class, and a `PrimaryPly` class for the edge properties of the `primary_plies` attribute. Other changes: - Add an `allowed_types_getter` parameter to `define_polymorphic_linked_object_list` with the same purpose as the existing `allowed_types`, except the types are evaluated only inside the getter. This was needed to avoid a circular import with the `ModelingGroup`, since `define_polymorphic_linked_object_list` is called at the module top-level. --- doc/source/api/linked_object_definitions.rst | 5 +- doc/source/api/tree_objects.rst | 1 + src/ansys/acp/core/__init__.py | 4 + src/ansys/acp/core/_tree_objects/__init__.py | 3 + .../_grpc_helpers/linked_object_list.py | 12 +- .../core/_tree_objects/butt_joint_sequence.py | 225 ++++++++++++++++++ .../acp/core/_tree_objects/modeling_group.py | 20 +- tests/unittests/test_butt_joint_sequence.py | 121 ++++++++++ 8 files changed, 383 insertions(+), 8 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/butt_joint_sequence.py create mode 100644 tests/unittests/test_butt_joint_sequence.py diff --git a/doc/source/api/linked_object_definitions.rst b/doc/source/api/linked_object_definitions.rst index 6f2168eca1..1383ae6641 100644 --- a/doc/source/api/linked_object_definitions.rst +++ b/doc/source/api/linked_object_definitions.rst @@ -7,7 +7,8 @@ Linked object definitions :toctree: _autosummary FabricWithAngle + Lamina LinkedSelectionRule - TaperEdge + PrimaryPly SubShape - Lamina + TaperEdge diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 861e42be11..d95ad469ac 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -8,6 +8,7 @@ ACP objects AnalysisPly BooleanSelectionRule + ButtJointSequence CADComponent CADGeometry CutoffSelectionRule diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 8e3566465b..2401fe59b6 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -48,6 +48,7 @@ BooleanSelectionRule, BooleanSelectionRuleElementalData, BooleanSelectionRuleNodalData, + ButtJointSequence, CADComponent, CADGeometry, CutoffMaterialType, @@ -105,6 +106,7 @@ PlyCutoffType, PlyGeometryExportFormat, PlyType, + PrimaryPly, ProductionPly, ProductionPlyElementalData, ProductionPlyNodalData, @@ -154,6 +156,7 @@ "BooleanSelectionRule", "BooleanSelectionRuleElementalData", "BooleanSelectionRuleNodalData", + "ButtJointSequence", "CADComponent", "CADGeometry", "ConnectLaunchConfig", @@ -223,6 +226,7 @@ "PlyCutoffType", "PlyGeometryExportFormat", "PlyType", + "PrimaryPly", "print_model", "ProductionPly", "ProductionPlyElementalData", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 2c7b24b34b..0b86c423bb 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -27,6 +27,7 @@ BooleanSelectionRuleElementalData, BooleanSelectionRuleNodalData, ) +from .butt_joint_sequence import ButtJointSequence, PrimaryPly from .cad_component import CADComponent from .cad_geometry import CADGeometry, TriangleMesh from .cutoff_selection_rule import ( @@ -127,6 +128,7 @@ "BooleanSelectionRule", "BooleanSelectionRuleElementalData", "BooleanSelectionRuleNodalData", + "ButtJointSequence", "CADComponent", "CADGeometry", "CutoffMaterialType", @@ -186,6 +188,7 @@ "PlyCutoffType", "PlyGeometryExportFormat", "PlyType", + "PrimaryPly", "ProductionPly", "ProductionPlyElementalData", "ProductionPlyNodalData", diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py index 4bd4b5d7f2..2fdbff54e5 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py @@ -41,7 +41,7 @@ ValueT = TypeVar("ValueT", bound=CreatableTreeObject) -__all__ = ["LinkedObjectList", "define_linked_object_list"] +__all__ = ["LinkedObjectList", "define_linked_object_list", "define_polymorphic_linked_object_list"] class LinkedObjectList(ObjectCacheMixin, MutableSequence[ValueT]): @@ -302,11 +302,19 @@ def setter(self: ValueT, value: list[ChildT]) -> None: def define_polymorphic_linked_object_list( - attribute_name: str, allowed_types: tuple[Any, ...] + attribute_name: str, + allowed_types: tuple[Any, ...] | None = None, + allowed_types_getter: Callable[[], tuple[Any, ...]] | None = None, ) -> Any: """Define a list of linked tree objects with polymorphic types.""" + if allowed_types is None != allowed_types_getter is None: + raise ValueError("Exactly one of allowed_types and allowed_types_getter must be provided.") def getter(self: ValueT) -> LinkedObjectList[Any]: + nonlocal allowed_types + if allowed_types_getter is not None: + allowed_types = allowed_types_getter() + return LinkedObjectList( _parent_object=self, _attribute_name=attribute_name, diff --git a/src/ansys/acp/core/_tree_objects/butt_joint_sequence.py b/src/ansys/acp/core/_tree_objects/butt_joint_sequence.py new file mode 100644 index 0000000000..c45d19a2a2 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/butt_joint_sequence.py @@ -0,0 +1,225 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Callable, Iterable, Sequence +from typing import TYPE_CHECKING, Any, Union, cast + +from typing_extensions import Self + +from ansys.api.acp.v0 import butt_joint_sequence_pb2, butt_joint_sequence_pb2_grpc + +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.edge_property_list import ( + GenericEdgePropertyType, + define_add_method, + define_edge_property_list, +) +from ._grpc_helpers.linked_object_list import define_polymorphic_linked_object_list +from ._grpc_helpers.polymorphic_from_pb import tree_object_from_resource_path +from ._grpc_helpers.property_helper import ( + _exposed_grpc_property, + grpc_data_property, + grpc_data_property_read_only, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .enums import status_type_from_pb +from .modeling_ply import ModelingPly +from .object_registry import register + +if TYPE_CHECKING: # pragma: no cover + # Creates a circular import if imported at the top-level, since the ButtJointSequence + # is a direct child of the ModelingGroup. + from .modeling_group import ModelingGroup + +__all__ = ["ButtJointSequence", "PrimaryPly"] + + +@mark_grpc_properties +class PrimaryPly(GenericEdgePropertyType): + """Defines a primary ply of a butt joint sequence. + + Parameters + ---------- + sequence : + Modeling group or modeling ply defining the primary ply. + level : + Level of the primary ply. Plies with a higher level inherit the thickness + from adjacent plies with a lower level. + + """ + + _SUPPORTED_SINCE = "25.1" + + def __init__(self, sequence: ModelingGroup | ModelingPly, level: int = 1): + self._callback_apply_changes: Callable[[], None] | None = None + self.sequence = sequence + self.level = level + + @_exposed_grpc_property + def sequence(self) -> ModelingGroup | ModelingPly: + """Linked sequence.""" + return self._sequence + + @sequence.setter + def sequence(self, value: ModelingGroup | ModelingPly) -> None: + from .modeling_group import ModelingGroup + + if not isinstance(value, (ModelingGroup, ModelingPly)): + raise TypeError(f"Expected a ModelingGroup or ModelingPly, got {type(value)}") + self._sequence = value + if self._callback_apply_changes: + self._callback_apply_changes() + + @_exposed_grpc_property + def level(self) -> int: + """Level of the primary ply. + + Plies with a higher level inherit the thickness from adjacent plies with a lower level. + """ + return self._level + + @level.setter + def level(self, value: int) -> None: + self._level = value + if self._callback_apply_changes: + self._callback_apply_changes() + + def _set_callback_apply_changes(self, callback_apply_changes: Callable[[], None]) -> None: + self._callback_apply_changes = callback_apply_changes + + @classmethod + def _from_pb_object( + cls, + parent_object: CreatableTreeObject, + message: butt_joint_sequence_pb2.PrimaryPly, + apply_changes: Callable[[], None], + ) -> Self: + from .modeling_group import ModelingGroup # imported here to avoid circular import + + new_obj = cls( + sequence=cast( + Union["ModelingGroup", ModelingPly], + tree_object_from_resource_path( + message.sequence, + server_wrapper=parent_object._server_wrapper, + allowed_types=(ModelingGroup, ModelingPly), + ), + ), + level=message.level, + ) + new_obj._set_callback_apply_changes(apply_changes) + return new_obj + + def _to_pb_object(self) -> butt_joint_sequence_pb2.PrimaryPly: + return butt_joint_sequence_pb2.PrimaryPly( + sequence=self.sequence._resource_path, level=self.level + ) + + def _check(self) -> bool: + # Check for empty resource paths + return bool(self.sequence._resource_path.value) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return ( + self.sequence._resource_path == other.sequence._resource_path + and self.level == other.level + ) + + return False + + def __repr__(self) -> str: + return f"PrimaryPly(sequence={self.sequence.__repr__()}, level={self.level})" + + def clone(self) -> Self: + """Create a new unstored PrimaryPly with the same properties.""" + return type(self)(sequence=self.sequence, level=self.level) + + +def _get_allowed_secondary_ply_types() -> tuple[type, ...]: + from .modeling_group import ModelingGroup + + return (ModelingGroup, ModelingPly) + + +@mark_grpc_properties +@register +class ButtJointSequence(CreatableTreeObject, IdTreeObject): + """Instantiate a ButtJointSequence. + + Parameters + ---------- + name : + Name of the butt joint sequence. + primary_plies : + Primary plies are the source of a butt joint and they pass the thickness to + adjacent plies. Plies with a higher level inherit the thickness from those + with a lower level. + secondary_plies : + Secondary plies are butt-joined to adjacent primary plies and they inherit + the thickness. + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "butt_joint_sequences" + _OBJECT_INFO_TYPE = butt_joint_sequence_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = butt_joint_sequence_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "ButtJointSequence", + active: bool = True, + global_ply_nr: int = 0, + primary_plies: Sequence[PrimaryPly] = (), + secondary_plies: Sequence[ModelingGroup | ModelingPly] = (), + ): + super().__init__(name=name) + self.active = active + self.global_ply_nr = global_ply_nr + self.primary_plies = primary_plies + self.secondary_plies = secondary_plies + + def _create_stub(self) -> butt_joint_sequence_pb2_grpc.ObjectServiceStub: + return butt_joint_sequence_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + global_ply_nr: ReadWriteProperty[int, int] = grpc_data_property("properties.global_ply_nr") + + primary_plies = define_edge_property_list("properties.primary_plies", PrimaryPly) + add_primary_ply = define_add_method( + PrimaryPly, + attribute_name="primary_plies", + func_name="add_primary_ply", + parent_class_name="ButtJointSequence", + module_name=__module__, + ) + + secondary_plies = define_polymorphic_linked_object_list( + "properties.secondary_plies", allowed_types_getter=_get_allowed_secondary_ply_types + ) diff --git a/src/ansys/acp/core/_tree_objects/modeling_group.py b/src/ansys/acp/core/_tree_objects/modeling_group.py index bb0058b669..5cc118a7ca 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_group.py +++ b/src/ansys/acp/core/_tree_objects/modeling_group.py @@ -26,6 +26,7 @@ import dataclasses from ansys.api.acp.v0 import ( + butt_joint_sequence_pb2_grpc, interface_layer_pb2_grpc, modeling_group_pb2, modeling_group_pb2_grpc, @@ -42,6 +43,7 @@ nodal_data_property, ) from .base import CreatableTreeObject, IdTreeObject +from .butt_joint_sequence import ButtJointSequence from .interface_layer import InterfaceLayer from .modeling_ply import ModelingPly from .object_registry import register @@ -68,7 +70,7 @@ class ModelingGroup(CreatableTreeObject, IdTreeObject): Parameters ---------- - name + name : Name of the modeling group. """ @@ -92,15 +94,25 @@ def _create_stub(self) -> modeling_group_pb2_grpc.ObjectServiceStub: module_name=__module__, ) modeling_plies = define_mutable_mapping(ModelingPly, modeling_ply_pb2_grpc.ObjectServiceStub) - interface_layers = define_mutable_mapping( - InterfaceLayer, interface_layer_pb2_grpc.ObjectServiceStub - ) create_interface_layer = define_create_method( InterfaceLayer, func_name="create_interface_layer", parent_class_name="ModelingGroup", module_name=__module__, ) + interface_layers = define_mutable_mapping( + InterfaceLayer, interface_layer_pb2_grpc.ObjectServiceStub + ) + + create_butt_joint_sequence = define_create_method( + ButtJointSequence, + func_name="create_butt_joint_sequence", + parent_class_name="ModelingGroup", + module_name=__module__, + ) + butt_joint_sequences = define_mutable_mapping( + ButtJointSequence, butt_joint_sequence_pb2_grpc.ObjectServiceStub + ) elemental_data = elemental_data_property(ModelingGroupElementalData) nodal_data = nodal_data_property(ModelingGroupNodalData) diff --git a/tests/unittests/test_butt_joint_sequence.py b/tests/unittests/test_butt_joint_sequence.py new file mode 100644 index 0000000000..9608100dd4 --- /dev/null +++ b/tests/unittests/test_butt_joint_sequence.py @@ -0,0 +1,121 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ButtJointSequence, PrimaryPly + +from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester +from .common.utils import AnyThing + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version( + ButtJointSequence._SUPPORTED_SINCE + ): + pytest.skip("ButtJointSequence is not supported on this version of the server.") + + +@pytest.fixture +def parent_model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def parent_object(parent_model): + return parent_model.modeling_groups["ModelingGroup.1"] + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_butt_joint_sequence() + + +class TestButtJointSequence(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "butt_joint_sequences" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "active": True, + "global_ply_nr": AnyThing(), + "primary_plies": [], + "secondary_plies": [], + } + + CREATE_METHOD_NAME = "create_butt_joint_sequence" + + @staticmethod + @pytest.fixture + def object_properties(parent_model): + mg1 = parent_model.create_modeling_group() + mg2 = parent_model.create_modeling_group() + mp1 = mg1.create_modeling_ply() + mp2 = mg1.create_modeling_ply() + return ObjectPropertiesToTest( + read_write=[ + ("name", "ButtJointSequence name"), + ("active", False), + ("global_ply_nr", 3), + ( + "primary_plies", + [ + PrimaryPly(sequence=mg1, level=1), + PrimaryPly(sequence=mp2, level=3), + ], + ), + ("secondary_plies", [mg2, mp1]), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) + + +def test_wrong_primary_ply_type_error_message(tree_object, parent_model): + butt_joint_sequence = tree_object + fabric = parent_model.create_fabric() + with pytest.raises(TypeError) as exc: + butt_joint_sequence.primary_plies = [fabric] + assert "PrimaryPly" in str(exc.value) + assert "Fabric" in str(exc.value) + + +def test_add_primary_ply(parent_object): + """Verify add method for primary plies.""" + modeling_ply_1 = parent_object.create_modeling_ply() + + butt_joint_sequence = parent_object.create_butt_joint_sequence() + butt_joint_sequence.add_primary_ply(modeling_ply_1) + assert butt_joint_sequence.primary_plies[-1].sequence == modeling_ply_1 + assert butt_joint_sequence.primary_plies[-1].level == 1 + modeling_ply_2 = modeling_ply_1.clone() + modeling_ply_2.store(parent=parent_object) + butt_joint_sequence.add_primary_ply(modeling_ply_2, level=3) + assert butt_joint_sequence.primary_plies[-1].sequence == modeling_ply_2 + assert butt_joint_sequence.primary_plies[-1].level == 3 From dca3001795b245812c33547aa7ba0b7693351665 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Fri, 25 Oct 2024 10:47:37 +0200 Subject: [PATCH 25/96] Fix network-limited benchmarks not running (#627) The benchmarks with specific network latency or bandwidth had failed to run since the `grpc_server` fixture was renamed to `acp_instance`, but the fixture which overrides it in the benchmarks was not. --- tests/benchmarks/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/benchmarks/conftest.py b/tests/benchmarks/conftest.py index fd7e7da0fb..a1ec388173 100644 --- a/tests/benchmarks/conftest.py +++ b/tests/benchmarks/conftest.py @@ -45,7 +45,7 @@ BENCHMARK_IMAGE_NAME = "pyacp-benchmark-runner" -def pytest_ignore_collect(collection_path, path, config): +def pytest_ignore_collect(collection_path, config): # The benchmarks can only be run on Linux, since the 'tc-netem' tool # used for manipulating network speeds is not available on Docker for # Windows / Mac. @@ -159,5 +159,5 @@ def network_options(request): @pytest.fixture -def grpc_server(_benchmark_servers, network_options): +def acp_instance(_benchmark_servers, network_options): return _benchmark_servers[network_options] From e80f2212b07f930974d65fe2506193cddf92660f Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Fri, 25 Oct 2024 11:08:38 +0200 Subject: [PATCH 26/96] Update API library dependency (#628) Update the `ansys-api-acp` version to `0.2.0`. Also update the minimum version of DPF Core, DPF Composites, and PyMechanical. These are used only in the examples, so the lower bound can be updated without breaking compatibility. The `ansys-dpf-core` dependency in particular was updated because otherwise it would downgrade to a version that doesn't limit the numpy upper version, see https://github.com/ansys/pydpf-core/issues/1831 --- poetry.lock | 1913 ++++++++++++++++++++++++++---------------------- pyproject.toml | 14 +- 2 files changed, 1033 insertions(+), 894 deletions(-) diff --git a/poetry.lock b/poetry.lock index c18377f615..acf59f8620 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,113 +20,113 @@ tests = ["hypothesis", "pytest"] [[package]] name = "aiohappyeyeballs" -version = "2.4.0" +version = "2.4.3" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, - {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, + {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, + {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, ] [[package]] name = "aiohttp" -version = "3.10.5" +version = "3.10.10" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, - {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, - {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, - {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, - {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, - {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, - {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, - {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, - {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, - {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, - {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, - {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, - {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, - {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, - {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, - {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68"}, + {file = "aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257"}, + {file = "aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a"}, + {file = "aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94"}, + {file = "aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205"}, + {file = "aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628"}, + {file = "aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b"}, + {file = "aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8"}, + {file = "aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b"}, + {file = "aiohttp-3.10.10-cp38-cp38-win32.whl", hash = "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c"}, + {file = "aiohttp-3.10.10-cp38-cp38-win_amd64.whl", hash = "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91"}, + {file = "aiohttp-3.10.10-cp39-cp39-win32.whl", hash = "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983"}, + {file = "aiohttp-3.10.10-cp39-cp39-win_amd64.whl", hash = "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23"}, + {file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"}, ] [package.dependencies] @@ -136,7 +136,7 @@ async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" +yarl = ">=1.12.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] @@ -168,13 +168,13 @@ files = [ [[package]] name = "ansys-api-acp" -version = "0.2.0.dev0" +version = "0.2.0" description = "Autogenerated Python package for the Ansys Composite PrepPost (ACP) gRPC API." optional = false python-versions = ">=3.7" files = [ - {file = "ansys_api_acp-0.2.0.dev0-py3-none-any.whl", hash = "sha256:888428ec47fbb9e5c9adf7f3a0eee3dd4e09e9c0606097b5e5ae3fdf29662534"}, - {file = "ansys_api_acp-0.2.0.dev0.tar.gz", hash = "sha256:a5d662f2ec279a97fd026f61d1fdc50a6367624d23c6efd080730dce8748a9a8"}, + {file = "ansys_api_acp-0.2.0-py3-none-any.whl", hash = "sha256:8aebea7150acf54b6cbbdc9bbac8bc7f7fdc71ea027a934d792c622926366764"}, + {file = "ansys_api_acp-0.2.0.tar.gz", hash = "sha256:177ad65573a844e7271fa9c1b692494f3b9a994e5eb9363ac7e7be645e7b73cd"}, ] [package.dependencies] @@ -370,13 +370,13 @@ vtk = ">=9.0.0" [[package]] name = "ansys-math-core" -version = "0.1.5" +version = "0.2.0" description = "A Python wrapper for PyAnsys Math libraries." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "ansys_math_core-0.1.5-py3-none-any.whl", hash = "sha256:5316aa088f68e550c5c6d5c59f86ed8983fed5906d4d4447cbe48e159a6ab162"}, - {file = "ansys_math_core-0.1.5.tar.gz", hash = "sha256:f088a13c716b03f06e60594c68524695582ffe03fc7f58e7d508f86f592d5f44"}, + {file = "ansys_math_core-0.2.0-py3-none-any.whl", hash = "sha256:68e554e45d8ec532cb4b5e3c0f902a035a047441654c20d36b0d25c71a11cf70"}, + {file = "ansys_math_core-0.2.0.tar.gz", hash = "sha256:3fea76c47118404feae198279bc8dc32e5c72f1fb0030f3ea34ff4c317a6789f"}, ] [package.dependencies] @@ -387,23 +387,23 @@ pyansys-tools-versioning = ">=0.3.3" scipy = ">=1.3.0" [package.extras] -doc = ["Sphinx (==7.3.7)", "ansys-mapdl-core (==0.68.1)", "ansys-mapdl-reader (==0.53.0)", "ansys-sphinx-theme (==0.15.2)", "jupyter_sphinx (==0.5.3)", "jupyterlab (==4.1.6)", "numpydoc (==1.7.0)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pyvista[jupyter,trame] (==0.43.5)", "scipy (==1.13.0)", "sphinx-autobuild (==2024.4.16)", "sphinx-autodoc-typehints (==2.1.0)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.5.0)", "sphinx-gallery (==0.15.0)", "sphinx-notfound-page (==1.0.0)", "trame (==3.6.0)", "vtk (==9.3.0)"] -tests = ["ansys-mapdl-core (==0.68.1)", "numpy (==1.26.4)", "pyansys-tools-report (==0.7.0)", "pytest (==8.1.1)", "pytest-cov (==5.0.0)", "pytest-rerunfailures (==14.0)", "pyvista (==0.43.5)", "scipy (==1.13.0)", "vtk (==9.3.0)"] +doc = ["Sphinx (==8.0.2)", "ansys-mapdl-core (==0.68.5)", "ansys-mapdl-reader (==0.54.1)", "ansys-sphinx-theme (==1.1.2)", "jupyter_sphinx (==0.5.3)", "jupyterlab (==4.2.5)", "numpydoc (==1.8.0)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pyvista[jupyter,trame] (==0.44.1)", "scipy (==1.14.1)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.4.4)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-gallery (==0.17.1)", "sphinx-notfound-page (==1.0.4)", "trame (==3.6.5)", "vtk (==9.3.1)"] +tests = ["ansys-mapdl-core (==0.68.5)", "numpy (==2.1.2)", "pyansys-tools-report (==0.8.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-rerunfailures (==14.0)", "pyvista (==0.44.1)", "scipy (==1.14.1)", "vtk (==9.3.1)"] [[package]] name = "ansys-mechanical-core" -version = "0.11.8" +version = "0.11.7" description = "A python wrapper for Ansys Mechanical" optional = false -python-versions = "<4.0,>=3.10" +python-versions = "<4.0,>=3.9" files = [ - {file = "ansys_mechanical_core-0.11.8-py3-none-any.whl", hash = "sha256:fc9cbce588f6ff9d8c877f4c83d59b90cbf36df75d8c994320bffd2d335a3cff"}, - {file = "ansys_mechanical_core-0.11.8.tar.gz", hash = "sha256:607f3d3fb89c13594d81be6692196727f723005aff112f51c4b26a233d3a8277"}, + {file = "ansys_mechanical_core-0.11.7-py3-none-any.whl", hash = "sha256:e13a98a87648ea80ad3c9d3901504d05c3500b359b0f88dfb06bc066e97783f1"}, + {file = "ansys_mechanical_core-0.11.7.tar.gz", hash = "sha256:6c54a5b4504ff3ffa3823e1c8e53c91b6d2f182c6fe4f6fd553f68a3e882f940"}, ] [package.dependencies] ansys-api-mechanical = "0.1.2" -ansys-mechanical-env = "0.1.8" +ansys-mechanical-env = "0.1.7" ansys-platform-instancemanagement = ">=1.0.1" ansys-pythonnet = ">=3.1.0rc2" ansys-tools-path = ">=0.3.1" @@ -412,23 +412,22 @@ click = ">=8.1.3" clr-loader = "0.2.6" grpcio = ">=1.30.0" protobuf = ">=3.12.2,<6" -psutil = "6.0.0" tqdm = ">=4.45.0" [package.extras] -doc = ["ansys-sphinx-theme[autoapi] (==1.1.4)", "grpcio (==1.66.2)", "imageio (==2.36.0)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "panel (==1.5.2)", "plotly (==5.24.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.1.3)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.5.0)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] -tests = ["psutil (==6.0.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.2)"] +doc = ["ansys-sphinx-theme[autoapi] (==1.0.7)", "grpcio (==1.66.0)", "imageio (==2.35.1)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.0)", "numpydoc (==1.8.0)", "pandas (==2.2.2)", "panel (==1.4.5)", "plotly (==5.23.0)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.0.2)", "sphinx-autobuild (==2024.4.16)", "sphinx-autodoc-typehints (==2.2.3)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.17.1)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] +tests = ["pytest (==8.3.2)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.0)"] viz = ["ansys-tools-visualization-interface (>=0.2.6)", "usd-core (==24.8)"] [[package]] name = "ansys-mechanical-env" -version = "0.1.8" +version = "0.1.7" description = "A python wrapper for loading environment variables when using PyMechanical embedded instances in Linux." optional = false -python-versions = "<4,>=3.10" +python-versions = "<4,>=3.9" files = [ - {file = "ansys_mechanical_env-0.1.8-py3-none-any.whl", hash = "sha256:4746c83924cba91137ffd367712a033abf2a8ccc66e7e02b76c1a3a6a80b4bf9"}, - {file = "ansys_mechanical_env-0.1.8.tar.gz", hash = "sha256:5286202292f6aeb3bf2278003059dbcd64774db2decb16a8cf68d15affef51d6"}, + {file = "ansys_mechanical_env-0.1.7-py3-none-any.whl", hash = "sha256:03004cc200a9c8e43fdc04b8879436f8089d4391daf98d7fac429bc997b62d6d"}, + {file = "ansys_mechanical_env-0.1.7.tar.gz", hash = "sha256:29a4ce796cf3719828f6af4fa7d1097333cb12d1b10b6ab199efe0f7ec8fc346"}, ] [package.dependencies] @@ -471,13 +470,13 @@ clr-loader = ">=0.2.6,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.1.6" +version = "1.1.7" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.10" files = [ - {file = "ansys_sphinx_theme-1.1.6-py3-none-any.whl", hash = "sha256:356593038adc5f407ba9ed862eda41babc99fe88063c6a990e3b4cc32b9e7a06"}, - {file = "ansys_sphinx_theme-1.1.6.tar.gz", hash = "sha256:a1c7afcfab172d7cd3b59b84d6c4ae6d703920ae5f682542193dd95e6fa3d81c"}, + {file = "ansys_sphinx_theme-1.1.7-py3-none-any.whl", hash = "sha256:fb67187650865e068d09d20a211e995a4b00e2d0dc2c67bd769811b438147663"}, + {file = "ansys_sphinx_theme-1.1.7.tar.gz", hash = "sha256:7f0bd36482b538fa76cf9d671ca3faa8a96d35f3ebc54674a6d1db73e8e9dc1d"}, ] [package.dependencies] @@ -546,13 +545,13 @@ tests = ["pyfakefs (==5.5.0)", "pytest (==8.2.1)", "pytest-cov (==5.0.0)"] [[package]] name = "ansys-tools-visualization-interface" -version = "0.4.5" +version = "0.4.7" description = "A Python visualization interface for PyAnsys libraries" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" files = [ - {file = "ansys_tools_visualization_interface-0.4.5-py3-none-any.whl", hash = "sha256:ac8d32be63ccf736a2c5246d4c3564aa38dcfffbf54db4ece41d5ac6abab4e8a"}, - {file = "ansys_tools_visualization_interface-0.4.5.tar.gz", hash = "sha256:3eef6907b5aa109230aa23185b14f6071ccebcb60a4a5dd4e706692700857f63"}, + {file = "ansys_tools_visualization_interface-0.4.7-py3-none-any.whl", hash = "sha256:c1555822395e3c75f39234f38a9ed999aba9e4bf047a02c820a1c7789cd926f6"}, + {file = "ansys_tools_visualization_interface-0.4.7.tar.gz", hash = "sha256:d173b71c46089ce4c21ccb2b1f690ae23712ce200b1d613d54a01f364cae9e7c"}, ] [package.dependencies] @@ -564,18 +563,18 @@ trame-vuetify = ">=2.4.3,<3" websockets = ">=12.0,<14" [package.extras] -doc = ["ansys-fluent-core (==0.24.2)", "ansys-sphinx-theme (==0.16.6)", "jupyter_sphinx (==0.5.3)", "jupytext (==1.16.3)", "nbsphinx (==0.9.4)", "numpydoc (==1.7.0)", "sphinx (==7.4.7)", "sphinx-autoapi (==3.2.1)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.17.0)", "sphinx-jinja (==2.0.2)", "sphinx_design (==0.6.0)"] -tests = ["pytest (==8.3.2)", "pytest-cov (==5.0.0)", "pytest-pyvista (==0.1.9)"] +doc = ["ansys-fluent-core (==0.26.1)", "ansys-sphinx-theme (==1.1.6)", "jupyter_sphinx (==0.5.3)", "jupytext (==1.16.4)", "nbsphinx (==0.9.5)", "numpydoc (==1.8.0)", "sphinx (==8.1.3)", "sphinx-autoapi (==3.3.2)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-jinja (==2.0.2)", "sphinx_design (==0.6.1)"] +tests = ["pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-pyvista (==0.1.9)"] [[package]] name = "anyio" -version = "4.4.0" +version = "4.6.2.post1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, + {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, + {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, ] [package.dependencies] @@ -585,9 +584,9 @@ sniffio = ">=1.1" typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "appdirs" @@ -981,101 +980,116 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -1220,83 +1234,73 @@ test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist" [[package]] name = "coverage" -version = "7.6.1" +version = "7.6.4" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, - {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, - {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, - {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, - {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, - {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, - {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, - {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, - {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, - {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, - {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, - {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, - {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, - {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, - {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, - {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, - {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, - {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, ] [package.dependencies] @@ -1322,33 +1326,37 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "debugpy" -version = "1.8.5" +version = "1.8.7" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7"}, - {file = "debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a"}, - {file = "debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed"}, - {file = "debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e"}, - {file = "debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a"}, - {file = "debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b"}, - {file = "debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408"}, - {file = "debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3"}, - {file = "debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156"}, - {file = "debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb"}, - {file = "debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7"}, - {file = "debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c"}, - {file = "debugpy-1.8.5-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a"}, - {file = "debugpy-1.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226"}, - {file = "debugpy-1.8.5-cp38-cp38-win32.whl", hash = "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a"}, - {file = "debugpy-1.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf"}, - {file = "debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c"}, - {file = "debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"}, - {file = "debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34"}, - {file = "debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c"}, - {file = "debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44"}, - {file = "debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0"}, + {file = "debugpy-1.8.7-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95fe04a573b8b22896c404365e03f4eda0ce0ba135b7667a1e57bd079793b96b"}, + {file = "debugpy-1.8.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628a11f4b295ffb4141d8242a9bb52b77ad4a63a2ad19217a93be0f77f2c28c9"}, + {file = "debugpy-1.8.7-cp310-cp310-win32.whl", hash = "sha256:85ce9c1d0eebf622f86cc68618ad64bf66c4fc3197d88f74bb695a416837dd55"}, + {file = "debugpy-1.8.7-cp310-cp310-win_amd64.whl", hash = "sha256:29e1571c276d643757ea126d014abda081eb5ea4c851628b33de0c2b6245b037"}, + {file = "debugpy-1.8.7-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:caf528ff9e7308b74a1749c183d6808ffbedbb9fb6af78b033c28974d9b8831f"}, + {file = "debugpy-1.8.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba1d078cf2e1e0b8402e6bda528bf8fda7ccd158c3dba6c012b7897747c41a0"}, + {file = "debugpy-1.8.7-cp311-cp311-win32.whl", hash = "sha256:171899588bcd412151e593bd40d9907133a7622cd6ecdbdb75f89d1551df13c2"}, + {file = "debugpy-1.8.7-cp311-cp311-win_amd64.whl", hash = "sha256:6e1c4ffb0c79f66e89dfd97944f335880f0d50ad29525dc792785384923e2211"}, + {file = "debugpy-1.8.7-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:4d27d842311353ede0ad572600c62e4bcd74f458ee01ab0dd3a1a4457e7e3706"}, + {file = "debugpy-1.8.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c1fd62ae0356e194f3e7b7a92acd931f71fe81c4b3be2c17a7b8a4b546ec2"}, + {file = "debugpy-1.8.7-cp312-cp312-win32.whl", hash = "sha256:2f729228430ef191c1e4df72a75ac94e9bf77413ce5f3f900018712c9da0aaca"}, + {file = "debugpy-1.8.7-cp312-cp312-win_amd64.whl", hash = "sha256:45c30aaefb3e1975e8a0258f5bbd26cd40cde9bfe71e9e5a7ac82e79bad64e39"}, + {file = "debugpy-1.8.7-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:d050a1ec7e925f514f0f6594a1e522580317da31fbda1af71d1530d6ea1f2b40"}, + {file = "debugpy-1.8.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f4349a28e3228a42958f8ddaa6333d6f8282d5edaea456070e48609c5983b7"}, + {file = "debugpy-1.8.7-cp313-cp313-win32.whl", hash = "sha256:11ad72eb9ddb436afb8337891a986302e14944f0f755fd94e90d0d71e9100bba"}, + {file = "debugpy-1.8.7-cp313-cp313-win_amd64.whl", hash = "sha256:2efb84d6789352d7950b03d7f866e6d180284bc02c7e12cb37b489b7083d81aa"}, + {file = "debugpy-1.8.7-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:4b908291a1d051ef3331484de8e959ef3e66f12b5e610c203b5b75d2725613a7"}, + {file = "debugpy-1.8.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da8df5b89a41f1fd31503b179d0a84a5fdb752dddd5b5388dbd1ae23cda31ce9"}, + {file = "debugpy-1.8.7-cp38-cp38-win32.whl", hash = "sha256:b12515e04720e9e5c2216cc7086d0edadf25d7ab7e3564ec8b4521cf111b4f8c"}, + {file = "debugpy-1.8.7-cp38-cp38-win_amd64.whl", hash = "sha256:93176e7672551cb5281577cdb62c63aadc87ec036f0c6a486f0ded337c504596"}, + {file = "debugpy-1.8.7-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:90d93e4f2db442f8222dec5ec55ccfc8005821028982f1968ebf551d32b28907"}, + {file = "debugpy-1.8.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6db2a370e2700557a976eaadb16243ec9c91bd46f1b3bb15376d7aaa7632c81"}, + {file = "debugpy-1.8.7-cp39-cp39-win32.whl", hash = "sha256:a6cf2510740e0c0b4a40330640e4b454f928c7b99b0c9dbf48b11efba08a8cda"}, + {file = "debugpy-1.8.7-cp39-cp39-win_amd64.whl", hash = "sha256:6a9d9d6d31846d8e34f52987ee0f1a904c7baa4912bf4843ab39dadf9b8f3e0d"}, + {file = "debugpy-1.8.7-py2.py3-none-any.whl", hash = "sha256:57b00de1c8d2c84a61b90880f7e5b6deaf4c312ecbde3a0e8912f2a56c4ac9ae"}, + {file = "debugpy-1.8.7.zip", hash = "sha256:18b8f731ed3e2e1df8e9cdaa23fb1fc9c24e570cd0081625308ec51c82efe42e"}, ] [[package]] @@ -1389,13 +1397,13 @@ files = [ [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] @@ -1475,69 +1483,75 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.16.0" +version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, - {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "fonttools" -version = "4.53.1" +version = "4.54.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, - {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, - {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, - {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, - {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, - {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, - {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, - {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, - {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, - {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, - {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, - {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, - {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, - {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, + {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, + {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, + {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, + {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, + {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, + {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, + {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, + {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, + {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, + {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, + {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, + {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, + {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, + {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, + {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, + {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, ] [package.extras] @@ -1567,88 +1581,103 @@ files = [ [[package]] name = "frozenlist" -version = "1.4.1" +version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, ] [[package]] @@ -1664,13 +1693,13 @@ files = [ [[package]] name = "google-api-core" -version = "2.19.2" +version = "2.21.0" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_core-2.19.2-py3-none-any.whl", hash = "sha256:53ec0258f2837dd53bbd3d3df50f5359281b3cc13f800c941dd15a9b5a415af4"}, - {file = "google_api_core-2.19.2.tar.gz", hash = "sha256:ca07de7e8aa1c98a8bfca9321890ad2340ef7f2eb136e558cee68f24b94b0a8f"}, + {file = "google_api_core-2.21.0-py3-none-any.whl", hash = "sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d"}, + {file = "google_api_core-2.21.0.tar.gz", hash = "sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81"}, ] [package.dependencies] @@ -1681,19 +1710,20 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 requests = ">=2.18.0,<3.0.0.dev0" [package.extras] +async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.145.0" +version = "2.149.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.145.0-py2.py3-none-any.whl", hash = "sha256:d74da1358f3f2d63daf3c6f26bd96d89652051183bc87cf10a56ceb2a70beb50"}, - {file = "google_api_python_client-2.145.0.tar.gz", hash = "sha256:8b84dde11aaccadc127e4846f5cd932331d804ea324e353131595e3f25376e97"}, + {file = "google_api_python_client-2.149.0-py2.py3-none-any.whl", hash = "sha256:1a5232e9cfed8c201799d9327e4d44dc7ea7daa3c6e1627fca41aa201539c0da"}, + {file = "google_api_python_client-2.149.0.tar.gz", hash = "sha256:b9d68c6b14ec72580d66001bd33c5816b78e2134b93ccc5cf8f624516b561750"}, ] [package.dependencies] @@ -1705,13 +1735,13 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.34.0" +version = "2.35.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.34.0-py2.py3-none-any.whl", hash = "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65"}, - {file = "google_auth-2.34.0.tar.gz", hash = "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc"}, + {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"}, + {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"}, ] [package.dependencies] @@ -1760,61 +1790,70 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "grpcio" -version = "1.66.1" +version = "1.67.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:4877ba180591acdf127afe21ec1c7ff8a5ecf0fe2600f0d3c50e8c4a1cbc6492"}, - {file = "grpcio-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3750c5a00bd644c75f4507f77a804d0189d97a107eb1481945a0cf3af3e7a5ac"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a013c5fbb12bfb5f927444b477a26f1080755a931d5d362e6a9a720ca7dbae60"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1b24c23d51a1e8790b25514157d43f0a4dce1ac12b3f0b8e9f66a5e2c4c132f"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffb8ea674d68de4cac6f57d2498fef477cef582f1fa849e9f844863af50083"}, - {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:307b1d538140f19ccbd3aed7a93d8f71103c5d525f3c96f8616111614b14bf2a"}, - {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c17ebcec157cfb8dd445890a03e20caf6209a5bd4ac5b040ae9dbc59eef091d"}, - {file = "grpcio-1.66.1-cp310-cp310-win32.whl", hash = "sha256:ef82d361ed5849d34cf09105d00b94b6728d289d6b9235513cb2fcc79f7c432c"}, - {file = "grpcio-1.66.1-cp310-cp310-win_amd64.whl", hash = "sha256:292a846b92cdcd40ecca46e694997dd6b9be6c4c01a94a0dfb3fcb75d20da858"}, - {file = "grpcio-1.66.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:c30aeceeaff11cd5ddbc348f37c58bcb96da8d5aa93fed78ab329de5f37a0d7a"}, - {file = "grpcio-1.66.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a1e224ce6f740dbb6b24c58f885422deebd7eb724aff0671a847f8951857c26"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a66fe4dc35d2330c185cfbb42959f57ad36f257e0cc4557d11d9f0a3f14311df"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ba04659e4fce609de2658fe4dbf7d6ed21987a94460f5f92df7579fd5d0e22"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4573608e23f7e091acfbe3e84ac2045680b69751d8d67685ffa193a4429fedb1"}, - {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7e06aa1f764ec8265b19d8f00140b8c4b6ca179a6dc67aa9413867c47e1fb04e"}, - {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3885f037eb11f1cacc41f207b705f38a44b69478086f40608959bf5ad85826dd"}, - {file = "grpcio-1.66.1-cp311-cp311-win32.whl", hash = "sha256:97ae7edd3f3f91480e48ede5d3e7d431ad6005bfdbd65c1b56913799ec79e791"}, - {file = "grpcio-1.66.1-cp311-cp311-win_amd64.whl", hash = "sha256:cfd349de4158d797db2bd82d2020554a121674e98fbe6b15328456b3bf2495bb"}, - {file = "grpcio-1.66.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:a92c4f58c01c77205df6ff999faa008540475c39b835277fb8883b11cada127a"}, - {file = "grpcio-1.66.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fdb14bad0835914f325349ed34a51940bc2ad965142eb3090081593c6e347be9"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f03a5884c56256e08fd9e262e11b5cfacf1af96e2ce78dc095d2c41ccae2c80d"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2559692d8e7e245d456877a85ee41525f3ed425aa97eb7a70fc9a79df91a0"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca1be089fb4446490dd1135828bd42a7c7f8421e74fa581611f7afdf7ab761"}, - {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d639c939ad7c440c7b2819a28d559179a4508783f7e5b991166f8d7a34b52815"}, - {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9feb4e5ec8dc2d15709f4d5fc367794d69277f5d680baf1910fc9915c633524"}, - {file = "grpcio-1.66.1-cp312-cp312-win32.whl", hash = "sha256:7101db1bd4cd9b880294dec41a93fcdce465bdbb602cd8dc5bd2d6362b618759"}, - {file = "grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734"}, - {file = "grpcio-1.66.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:ecfe735e7a59e5a98208447293ff8580e9db1e890e232b8b292dc8bd15afc0d2"}, - {file = "grpcio-1.66.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4825a3aa5648010842e1c9d35a082187746aa0cdbf1b7a2a930595a94fb10fce"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f517fd7259fe823ef3bd21e508b653d5492e706e9f0ef82c16ce3347a8a5620c"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1fe60d0772831d96d263b53d83fb9a3d050a94b0e94b6d004a5ad111faa5b5b"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31a049daa428f928f21090403e5d18ea02670e3d5d172581670be006100db9ef"}, - {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f914386e52cbdeb5d2a7ce3bf1fdfacbe9d818dd81b6099a05b741aaf3848bb"}, - {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bff2096bdba686019fb32d2dde45b95981f0d1490e054400f70fc9a8af34b49d"}, - {file = "grpcio-1.66.1-cp38-cp38-win32.whl", hash = "sha256:aa8ba945c96e73de29d25331b26f3e416e0c0f621e984a3ebdb2d0d0b596a3b3"}, - {file = "grpcio-1.66.1-cp38-cp38-win_amd64.whl", hash = "sha256:161d5c535c2bdf61b95080e7f0f017a1dfcb812bf54093e71e5562b16225b4ce"}, - {file = "grpcio-1.66.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:d0cd7050397b3609ea51727b1811e663ffda8bda39c6a5bb69525ef12414b503"}, - {file = "grpcio-1.66.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0e6c9b42ded5d02b6b1fea3a25f036a2236eeb75d0579bfd43c0018c88bf0a3e"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c9f80f9fad93a8cf71c7f161778ba47fd730d13a343a46258065c4deb4b550c0"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dd67ed9da78e5121efc5c510f0122a972216808d6de70953a740560c572eb44"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48b0d92d45ce3be2084b92fb5bae2f64c208fea8ceed7fccf6a7b524d3c4942e"}, - {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d813316d1a752be6f5c4360c49f55b06d4fe212d7df03253dfdae90c8a402bb"}, - {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c9bebc6627873ec27a70fc800f6083a13c70b23a5564788754b9ee52c5aef6c"}, - {file = "grpcio-1.66.1-cp39-cp39-win32.whl", hash = "sha256:30a1c2cf9390c894c90bbc70147f2372130ad189cffef161f0432d0157973f45"}, - {file = "grpcio-1.66.1-cp39-cp39-win_amd64.whl", hash = "sha256:17663598aadbedc3cacd7bbde432f541c8e07d2496564e22b214b22c7523dac8"}, - {file = "grpcio-1.66.1.tar.gz", hash = "sha256:35334f9c9745add3e357e3372756fd32d925bd52c41da97f4dfdafbde0bf0ee2"}, + {file = "grpcio-1.67.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc"}, + {file = "grpcio-1.67.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8"}, + {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8"}, + {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4"}, + {file = "grpcio-1.67.0-cp310-cp310-win32.whl", hash = "sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65"}, + {file = "grpcio-1.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6"}, + {file = "grpcio-1.67.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4"}, + {file = "grpcio-1.67.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8"}, + {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf"}, + {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23"}, + {file = "grpcio-1.67.0-cp311-cp311-win32.whl", hash = "sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8"}, + {file = "grpcio-1.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772"}, + {file = "grpcio-1.67.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d"}, + {file = "grpcio-1.67.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52"}, + {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81"}, + {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3"}, + {file = "grpcio-1.67.0-cp312-cp312-win32.whl", hash = "sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955"}, + {file = "grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15"}, + {file = "grpcio-1.67.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a"}, + {file = "grpcio-1.67.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c"}, + {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153"}, + {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03"}, + {file = "grpcio-1.67.0-cp313-cp313-win32.whl", hash = "sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69"}, + {file = "grpcio-1.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210"}, + {file = "grpcio-1.67.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:7f200aca719c1c5dc72ab68be3479b9dafccdf03df530d137632c534bb6f1ee3"}, + {file = "grpcio-1.67.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0892dd200ece4822d72dd0952f7112c542a487fc48fe77568deaaa399c1e717d"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f4d613fbf868b2e2444f490d18af472ccb47660ea3df52f068c9c8801e1f3e85"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c69bf11894cad9da00047f46584d5758d6ebc9b5950c0dc96fec7e0bce5cde9"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9bca3ca0c5e74dea44bf57d27e15a3a3996ce7e5780d61b7c72386356d231db"}, + {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:014dfc020e28a0d9be7e93a91f85ff9f4a87158b7df9952fe23cc42d29d31e1e"}, + {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4ea4509d42c6797539e9ec7496c15473177ce9abc89bc5c71e7abe50fc25737"}, + {file = "grpcio-1.67.0-cp38-cp38-win32.whl", hash = "sha256:9d75641a2fca9ae1ae86454fd25d4c298ea8cc195dbc962852234d54a07060ad"}, + {file = "grpcio-1.67.0-cp38-cp38-win_amd64.whl", hash = "sha256:cff8e54d6a463883cda2fab94d2062aad2f5edd7f06ae3ed030f2a74756db365"}, + {file = "grpcio-1.67.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:62492bd534979e6d7127b8a6b29093161a742dee3875873e01964049d5250a74"}, + {file = "grpcio-1.67.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eef1dce9d1a46119fd09f9a992cf6ab9d9178b696382439446ca5f399d7b96fe"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f623c57a5321461c84498a99dddf9d13dac0e40ee056d884d6ec4ebcab647a78"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54d16383044e681f8beb50f905249e4e7261dd169d4aaf6e52eab67b01cbbbe2"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a44e572fb762c668e4812156b81835f7aba8a721b027e2d4bb29fb50ff4d33"}, + {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:391df8b0faac84d42f5b8dfc65f5152c48ed914e13c522fd05f2aca211f8bfad"}, + {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfd9306511fdfc623a1ba1dc3bc07fbd24e6cfbe3c28b4d1e05177baa2f99617"}, + {file = "grpcio-1.67.0-cp39-cp39-win32.whl", hash = "sha256:30d47dbacfd20cbd0c8be9bfa52fdb833b395d4ec32fe5cff7220afc05d08571"}, + {file = "grpcio-1.67.0-cp39-cp39-win_amd64.whl", hash = "sha256:f55f077685f61f0fbd06ea355142b71e47e4a26d2d678b3ba27248abfe67163a"}, + {file = "grpcio-1.67.0.tar.gz", hash = "sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.66.1)"] +protobuf = ["grpcio-tools (>=1.67.0)"] [[package]] name = "grpcio-health-checking" @@ -1986,13 +2025,13 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio [[package]] name = "ipython" -version = "8.27.0" +version = "8.28.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.27.0-py3-none-any.whl", hash = "sha256:f68b3cb8bde357a5d7adc9598d57e22a45dfbea19eb6b98286fa3b288c9cd55c"}, - {file = "ipython-8.27.0.tar.gz", hash = "sha256:0b99a2dc9f15fd68692e898e5568725c6d49c527d36a9fb5960ffbdeaa82ff7e"}, + {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, + {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, ] [package.dependencies] @@ -2135,13 +2174,13 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.12.1" +version = "2024.10.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, - {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, ] [package.dependencies] @@ -2440,82 +2479,83 @@ files = [ [[package]] name = "makefun" -version = "1.15.4" +version = "1.15.6" description = "Small library to dynamically create python functions." optional = false python-versions = "*" files = [ - {file = "makefun-1.15.4-py2.py3-none-any.whl", hash = "sha256:945d078a7e01a903f2cbef738b33e0ebc52b8d35fb7e20c528ed87b5c80db5b7"}, - {file = "makefun-1.15.4.tar.gz", hash = "sha256:9f9b9904e7c397759374a88f4c57781fbab2a458dec78df4b3ee6272cd9fb010"}, + {file = "makefun-1.15.6-py2.py3-none-any.whl", hash = "sha256:e69b870f0bb60304765b1e3db576aaecf2f9b3e5105afe8cfeff8f2afe6ad067"}, + {file = "makefun-1.15.6.tar.gz", hash = "sha256:26bc63442a6182fb75efed8b51741dd2d1db2f176bec8c64e20a586256b8f149"}, ] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] @@ -2796,43 +2836,43 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.12.1" +version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801"}, - {file = "mypy-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5"}, - {file = "mypy-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1"}, - {file = "mypy-1.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627"}, - {file = "mypy-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20"}, - {file = "mypy-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735"}, - {file = "mypy-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66"}, - {file = "mypy-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6"}, - {file = "mypy-1.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931"}, - {file = "mypy-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811"}, - {file = "mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f"}, - {file = "mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0"}, - {file = "mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042"}, - {file = "mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179"}, - {file = "mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a"}, - {file = "mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc"}, - {file = "mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635"}, - {file = "mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81"}, - {file = "mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4"}, - {file = "mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02"}, - {file = "mypy-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b16fe09f9c741d85a2e3b14a5257a27a4f4886c171d562bc5a5e90d8591906b8"}, - {file = "mypy-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0dcc1e843d58f444fce19da4cce5bd35c282d4bde232acdeca8279523087088a"}, - {file = "mypy-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10ba7de5c616e44ad21005fa13450cd0de7caaa303a626147d45307492e4f2d"}, - {file = "mypy-1.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e6fe449223fa59fbee351db32283838a8fee8059e0028e9e6494a03802b4004"}, - {file = "mypy-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc6e2a2195a290a7fd5bac3e60b586d77fc88e986eba7feced8b778c373f9afe"}, - {file = "mypy-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:de5b2a8988b4e1269a98beaf0e7cc71b510d050dce80c343b53b4955fff45f19"}, - {file = "mypy-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843826966f1d65925e8b50d2b483065c51fc16dc5d72647e0236aae51dc8d77e"}, - {file = "mypy-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe20f89da41a95e14c34b1ddb09c80262edcc295ad891f22cc4b60013e8f78d"}, - {file = "mypy-1.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8135ffec02121a75f75dc97c81af7c14aa4ae0dda277132cfcd6abcd21551bfd"}, - {file = "mypy-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a7b76fa83260824300cc4834a3ab93180db19876bce59af921467fd03e692810"}, - {file = "mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e"}, - {file = "mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] @@ -2842,6 +2882,7 @@ typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -2950,13 +2991,13 @@ files = [ [[package]] name = "networkx" -version = "3.4.1" +version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" files = [ - {file = "networkx-3.4.1-py3-none-any.whl", hash = "sha256:e30a87b48c9a6a7cc220e732bffefaee585bdb166d13377734446ce1a0620eed"}, - {file = "networkx-3.4.1.tar.gz", hash = "sha256:f9df45e85b78f5bd010993e897b4f1fdb242c11e015b101bd951e5c0e29982d8"}, + {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, + {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, ] [package.extras] @@ -3133,95 +3174,90 @@ ptyprocess = ">=0.5" [[package]] name = "pillow" -version = "10.4.0" +version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -3230,13 +3266,13 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.3.3" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, - {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] @@ -3300,13 +3336,13 @@ virtualenv = ">=20.10.0" [[package]] name = "prometheus-client" -version = "0.20.0" +version = "0.21.0" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.8" files = [ - {file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"}, - {file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"}, + {file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"}, + {file = "prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e"}, ] [package.extras] @@ -3314,18 +3350,125 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] wcwidth = "*" +[[package]] +name = "propcache" +version = "0.2.0" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.8" +files = [ + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, + {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, + {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, + {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, + {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, + {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, + {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, + {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, + {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, + {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, + {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, + {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, + {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, + {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, + {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, +] + [[package]] name = "proto-plus" version = "1.24.0" @@ -3345,52 +3488,53 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "4.25.4" +version = "4.25.5" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4"}, - {file = "protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d"}, - {file = "protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b"}, - {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835"}, - {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040"}, - {file = "protobuf-4.25.4-cp38-cp38-win32.whl", hash = "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1"}, - {file = "protobuf-4.25.4-cp38-cp38-win_amd64.whl", hash = "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1"}, - {file = "protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca"}, - {file = "protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f"}, - {file = "protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978"}, - {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"}, + {file = "protobuf-4.25.5-cp310-abi3-win32.whl", hash = "sha256:5e61fd921603f58d2f5acb2806a929b4675f8874ff5f330b7d6f7e2e784bbcd8"}, + {file = "protobuf-4.25.5-cp310-abi3-win_amd64.whl", hash = "sha256:4be0571adcbe712b282a330c6e89eae24281344429ae95c6d85e79e84780f5ea"}, + {file = "protobuf-4.25.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b2fde3d805354df675ea4c7c6338c1aecd254dfc9925e88c6d31a2bcb97eb173"}, + {file = "protobuf-4.25.5-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:919ad92d9b0310070f8356c24b855c98df2b8bd207ebc1c0c6fcc9ab1e007f3d"}, + {file = "protobuf-4.25.5-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fe14e16c22be926d3abfcb500e60cab068baf10b542b8c858fa27e098123e331"}, + {file = "protobuf-4.25.5-cp38-cp38-win32.whl", hash = "sha256:98d8d8aa50de6a2747efd9cceba361c9034050ecce3e09136f90de37ddba66e1"}, + {file = "protobuf-4.25.5-cp38-cp38-win_amd64.whl", hash = "sha256:b0234dd5a03049e4ddd94b93400b67803c823cfc405689688f59b34e0742381a"}, + {file = "protobuf-4.25.5-cp39-cp39-win32.whl", hash = "sha256:abe32aad8561aa7cc94fc7ba4fdef646e576983edb94a73381b03c53728a626f"}, + {file = "protobuf-4.25.5-cp39-cp39-win_amd64.whl", hash = "sha256:7a183f592dc80aa7c8da7ad9e55091c4ffc9497b3054452d629bb85fa27c2a45"}, + {file = "protobuf-4.25.5-py3-none-any.whl", hash = "sha256:0aebecb809cae990f8129ada5ca273d9d670b76d9bfc9b1809f0a9c02b7dbf41"}, + {file = "protobuf-4.25.5.tar.gz", hash = "sha256:7f8249476b4a9473645db7f8ab42b02fe1488cbe5fb72fddd445e0665afd8584"}, ] [[package]] name = "psutil" -version = "6.0.0" +version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, - {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, - {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, - {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, - {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, - {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, - {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, - {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, - {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, - {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, + {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, + {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, + {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, + {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "ptyprocess" @@ -3554,13 +3698,13 @@ files = [ [[package]] name = "pyparsing" -version = "3.1.4" +version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" files = [ - {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, - {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, + {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, + {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, ] [package.extras] @@ -3703,40 +3847,44 @@ jupyter = ["ipywidgets", "jupyter-server-proxy", "nest-asyncio", "trame (>=2.5.2 [[package]] name = "pywin32" -version = "306" +version = "308" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, + {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, + {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, + {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, + {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, + {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, + {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, + {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, + {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, + {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, + {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, + {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, + {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, + {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, + {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, + {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, + {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, + {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, + {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, ] [[package]] name = "pywinpty" -version = "2.0.13" +version = "2.0.14" description = "Pseudo terminal support for Windows from Python." optional = false python-versions = ">=3.8" files = [ - {file = "pywinpty-2.0.13-cp310-none-win_amd64.whl", hash = "sha256:697bff211fb5a6508fee2dc6ff174ce03f34a9a233df9d8b5fe9c8ce4d5eaf56"}, - {file = "pywinpty-2.0.13-cp311-none-win_amd64.whl", hash = "sha256:b96fb14698db1284db84ca38c79f15b4cfdc3172065b5137383910567591fa99"}, - {file = "pywinpty-2.0.13-cp312-none-win_amd64.whl", hash = "sha256:2fd876b82ca750bb1333236ce98488c1be96b08f4f7647cfdf4129dfad83c2d4"}, - {file = "pywinpty-2.0.13-cp38-none-win_amd64.whl", hash = "sha256:61d420c2116c0212808d31625611b51caf621fe67f8a6377e2e8b617ea1c1f7d"}, - {file = "pywinpty-2.0.13-cp39-none-win_amd64.whl", hash = "sha256:71cb613a9ee24174730ac7ae439fd179ca34ccb8c5349e8d7b72ab5dea2c6f4b"}, - {file = "pywinpty-2.0.13.tar.gz", hash = "sha256:c34e32351a3313ddd0d7da23d27f835c860d32fe4ac814d372a3ea9594f41dde"}, + {file = "pywinpty-2.0.14-cp310-none-win_amd64.whl", hash = "sha256:0b149c2918c7974f575ba79f5a4aad58bd859a52fa9eb1296cc22aa412aa411f"}, + {file = "pywinpty-2.0.14-cp311-none-win_amd64.whl", hash = "sha256:cf2a43ac7065b3e0dc8510f8c1f13a75fb8fde805efa3b8cff7599a1ef497bc7"}, + {file = "pywinpty-2.0.14-cp312-none-win_amd64.whl", hash = "sha256:55dad362ef3e9408ade68fd173e4f9032b3ce08f68cfe7eacb2c263ea1179737"}, + {file = "pywinpty-2.0.14-cp313-none-win_amd64.whl", hash = "sha256:074fb988a56ec79ca90ed03a896d40707131897cefb8f76f926e3834227f2819"}, + {file = "pywinpty-2.0.14-cp39-none-win_amd64.whl", hash = "sha256:5725fd56f73c0531ec218663bd8c8ff5acc43c78962fab28564871b5fce053fd"}, + {file = "pywinpty-2.0.14.tar.gz", hash = "sha256:18bd9529e4a5daf2d9719aa17788ba6013e594ae94c5a0c27e83df3278b0660e"}, ] [[package]] @@ -4191,13 +4339,13 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "75.1.0" +version = "75.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, - {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, + {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, + {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, ] [package.extras] @@ -4583,13 +4731,13 @@ test = ["pytest", "ruff"] [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] @@ -4649,29 +4797,29 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "trame" -version = "3.6.5" +version = "3.7.0" description = "Trame, a framework to build applications in plain Python" optional = false python-versions = "*" files = [ - {file = "trame-3.6.5-py3-none-any.whl", hash = "sha256:58b5be29287d275dbd2be15b4e453c788361f640253ad153ca99fb3eb28e00f3"}, - {file = "trame-3.6.5.tar.gz", hash = "sha256:fc26a7067f94377e01263facb4cd40b111d799d970b5fc8e2aecb52e60378959"}, + {file = "trame-3.7.0-py3-none-any.whl", hash = "sha256:8af7c0d91749a18e6a4098aced90ff9b7aec3a40fa089e85d6ea8b5623353349"}, + {file = "trame-3.7.0.tar.gz", hash = "sha256:c2cc3c81b6be2b480584ecf789397e0925896b0d94eb8e807ae847e91e3b8850"}, ] [package.dependencies] -trame-client = ">=3,<4" -trame-server = ">=3,<4" +trame-client = ">=3.4,<4" +trame-server = ">=3.2.3,<4" wslink = ">=2.1.3" [[package]] name = "trame-client" -version = "3.2.5" +version = "3.4.0" description = "Internal client of trame" optional = false python-versions = "*" files = [ - {file = "trame-client-3.2.5.tar.gz", hash = "sha256:6f81c2a78389f1962bec38a318b0de47f241dae7778b25dba75fa3ed4d58bd33"}, - {file = "trame_client-3.2.5-py3-none-any.whl", hash = "sha256:023e062175fef67d45a65ac7a367b0e3468f6746c4f8c7a7aa3fe8e82273c62a"}, + {file = "trame-client-3.4.0.tar.gz", hash = "sha256:60df253adcf640df41e4e769cfe13f298f4a5b6883a604d603479c7bcd3da6cb"}, + {file = "trame_client-3.4.0-py3-none-any.whl", hash = "sha256:59f45597fe18118d67f702b6d294c3bf75d1e84269dde0c0baa5da1a59d9a453"}, ] [package.extras] @@ -4679,13 +4827,13 @@ test = ["Pillow", "pixelmatch", "pytest", "pytest-xprocess", "seleniumbase"] [[package]] name = "trame-server" -version = "3.2.0" +version = "3.2.3" description = "Internal server side implementation of trame" optional = false python-versions = "*" files = [ - {file = "trame-server-3.2.0.tar.gz", hash = "sha256:85969d603ae084cc47678972948b1a6e255e8581ea4c7cb99c6b414425ac952f"}, - {file = "trame_server-3.2.0-py3-none-any.whl", hash = "sha256:7cf747a7e3399c3de630311675ef432403775e9ac49c11de2fd9a9a5f92702d7"}, + {file = "trame-server-3.2.3.tar.gz", hash = "sha256:4b5d38c17f6c2e8a7bd4644a1d45e2bd79df9829c4ae24e987633754748311f2"}, + {file = "trame_server-3.2.3-py3-none-any.whl", hash = "sha256:40a8ca401893ec91e1ee09ccf674a75ce81a4695916e71412d74612ebd045d8f"}, ] [package.dependencies] @@ -4694,13 +4842,13 @@ wslink = ">=2.2.1,<3" [[package]] name = "trame-vtk" -version = "2.8.10" +version = "2.8.11" description = "VTK widgets for trame" optional = false python-versions = "*" files = [ - {file = "trame-vtk-2.8.10.tar.gz", hash = "sha256:4d8e38f7c1d5be8eafc28254bd5bd5dc5d4b336a334b944fc9383c9624184b5e"}, - {file = "trame_vtk-2.8.10-py3-none-any.whl", hash = "sha256:0fe850d1358193c1d73c9af87715d7877210df53edf4e4d468ba396c0845765c"}, + {file = "trame-vtk-2.8.11.tar.gz", hash = "sha256:cd8391181fc8dbfee1f24dad2d7377db8aab5b012362108b985da88b4b271ac3"}, + {file = "trame_vtk-2.8.11-py3-none-any.whl", hash = "sha256:e00d2500e692474204bc015ced1f701d6a504a8f68e5f858f67b1fe737160106"}, ] [package.dependencies] @@ -4733,13 +4881,13 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20240906" +version = "2.9.0.20241003" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.9.0.20240906.tar.gz", hash = "sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e"}, - {file = "types_python_dateutil-2.9.0.20240906-py3-none-any.whl", hash = "sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6"}, + {file = "types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446"}, + {file = "types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d"}, ] [[package]] @@ -4797,13 +4945,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.4" +version = "20.27.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, - {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, + {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, + {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, ] [package.dependencies] @@ -5035,108 +5183,99 @@ ssl = ["cryptography"] [[package]] name = "yarl" -version = "1.11.1" +version = "1.16.0" description = "Yet another URL library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, - {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, - {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, - {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, - {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, - {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, - {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, - {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, - {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, - {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, - {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, - {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, - {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, - {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, - {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, - {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, - {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, + {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058"}, + {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2"}, + {file = "yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b"}, + {file = "yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929"}, + {file = "yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7"}, + {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3"}, + {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2"}, + {file = "yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56"}, + {file = "yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c"}, + {file = "yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d"}, + {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104"}, + {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6"}, + {file = "yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3"}, + {file = "yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67"}, + {file = "yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240"}, + {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ace0177520bd4caa99295a9b6fb831d0e9a57d8e0501a22ffaa61b4c024283"}, + {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7118bdb5e3ed81acaa2095cba7ec02a0fe74b52a16ab9f9ac8e28e53ee299732"}, + {file = "yarl-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38fec8a2a94c58bd47c9a50a45d321ab2285ad133adefbbadf3012c054b7e656"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8791d66d81ee45866a7bb15a517b01a2bcf583a18ebf5d72a84e6064c417e64b"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cf936ba67bc6c734f3aa1c01391da74ab7fc046a9f8bbfa230b8393b90cf472"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1aab176dd55b59f77a63b27cffaca67d29987d91a5b615cbead41331e6b7428"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995d0759004c08abd5d1b81300a91d18c8577c6389300bed1c7c11675105a44d"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bc22e00edeb068f71967ab99081e9406cd56dbed864fc3a8259442999d71552"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35b4f7842154176523e0a63c9b871168c69b98065d05a4f637fce342a6a2693a"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7ace71c4b7a0c41f317ae24be62bb61e9d80838d38acb20e70697c625e71f120"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8f639e3f5795a6568aa4f7d2ac6057c757dcd187593679f035adbf12b892bb00"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e8be3aff14f0120ad049121322b107f8a759be76a6a62138322d4c8a337a9e2c"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:122d8e7986043d0549e9eb23c7fd23be078be4b70c9eb42a20052b3d3149c6f2"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fd9c227990f609c165f56b46107d0bc34553fe0387818c42c02f77974402c36"}, + {file = "yarl-1.16.0-cp313-cp313-win32.whl", hash = "sha256:595ca5e943baed31d56b33b34736461a371c6ea0038d3baec399949dd628560b"}, + {file = "yarl-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:921b81b8d78f0e60242fb3db615ea3f368827a76af095d5a69f1c3366db3f596"}, + {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab2b2ac232110a1fdb0d3ffcd087783edd3d4a6ced432a1bf75caf7b7be70916"}, + {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f8713717a09acbfee7c47bfc5777e685539fefdd34fa72faf504c8be2f3df4e"}, + {file = "yarl-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdcffe1dbcb4477d2b4202f63cd972d5baa155ff5a3d9e35801c46a415b7f71a"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a91217208306d82357c67daeef5162a41a28c8352dab7e16daa82e3718852a7"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ab3ed42c78275477ea8e917491365e9a9b69bb615cb46169020bd0aa5e2d6d3"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:707ae579ccb3262dfaef093e202b4c3fb23c3810e8df544b1111bd2401fd7b09"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7a852d1cd0b8d8b37fc9d7f8581152add917a98cfe2ea6e241878795f917ae"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3f1cc3d3d4dc574bebc9b387f6875e228ace5748a7c24f49d8f01ac1bc6c31b"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5ff96da263740779b0893d02b718293cc03400c3a208fc8d8cd79d9b0993e532"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3d375a19ba2bfe320b6d873f3fb165313b002cef8b7cc0a368ad8b8a57453837"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:62c7da0ad93a07da048b500514ca47b759459ec41924143e2ddb5d7e20fd3db5"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:147b0fcd0ee33b4b5f6edfea80452d80e419e51b9a3f7a96ce98eaee145c1581"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:504e1fe1cc4f170195320eb033d2b0ccf5c6114ce5bf2f617535c01699479bca"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bdcf667a5dec12a48f669e485d70c54189f0639c2157b538a4cffd24a853624f"}, + {file = "yarl-1.16.0-cp39-cp39-win32.whl", hash = "sha256:e9951afe6557c75a71045148890052cb942689ee4c9ec29f5436240e1fcc73b7"}, + {file = "yarl-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d7aaa8ff95d0840e289423e7dc35696c2b058d635f945bf05b5cd633146b027"}, + {file = "yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3"}, + {file = "yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +propcache = ">=0.2.0" [[package]] name = "zipp" @@ -5163,4 +5302,4 @@ examples = ["ansys-dpf-composites", "ansys-mapdl-core", "ansys-mechanical-core", [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "b387ab7ac9a40f004afc5eb3571f76df4294625c4b70c3b0e77e32f2d8293c44" +content-hash = "9e737ebe952748709d3c3b8c177d1aebe61367f4dfee36573d6a17c3289e39f6" diff --git a/pyproject.toml b/pyproject.toml index 22a6327e03..9580aabcc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ numpy = ">=1.22" grpcio-health-checking = ">=1.43" packaging = ">=15.0" typing-extensions = ">=4.5.0" -ansys-api-acp = "==0.2.0.dev0" +ansys-api-acp = "==0.2.0" ansys-tools-path = ">=0" ansys-tools-local-product-launcher = ">=0.1" ansys-tools-filetransfer = ">=0.1" @@ -47,9 +47,9 @@ networkx = ">=3.0.0" # Dependencies for the examples. Update also the 'dev' group when # these are updated. ansys-mapdl-core = { version = ">=0.68.3", optional = true } -ansys-dpf-composites = { version = ">=0.3", optional = true } -ansys-dpf-core = { version = ">=0.8", optional = true} -ansys-mechanical-core = { version = ">=0.10.0", optional = true, python = "<3.12" } +ansys-dpf-composites = { version = ">=0.6", optional = true } +ansys-dpf-core = { version = ">=0.13", optional = true} +ansys-mechanical-core = { version = ">=0.10.0", optional = true } matplotlib = { version = ">=3.8.3", optional = true } scipy = { version = ">=1.12.0", optional = true } @@ -79,9 +79,9 @@ pyvista = { version = ">=0.42.0", extras = ["jupyter", "trame"] } # included in the 'examples' extra. This is done s.t. the install # flag '--with=dev,test' will install all dependencies. ansys-mapdl-core = ">=0.68.3" -ansys-dpf-composites = { version = ">=0.3"} -ansys-dpf-core = { version = ">=0.8"} -ansys-mechanical-core = { version = ">=0.10.0", python = "<3.12" } +ansys-dpf-composites = { version = ">=0.6"} +ansys-dpf-core = { version = ">=0.13"} +ansys-mechanical-core = { version = ">=0.10.0" } matplotlib = ">=3.8.3" scipy = ">=1.12.0" From 0eb9d597e43bc9420b7fc6ffefb91ddd13dfe9bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 06:59:33 +0100 Subject: [PATCH 27/96] Bump the dependencies group with 2 updates (#632) Bumps the dependencies group with 2 updates: [ansys-mechanical-core](https://github.com/ansys/pymechanical) and [hypothesis](https://github.com/HypothesisWorks/hypothesis). Updates `ansys-mechanical-core` from 0.11.7 to 0.11.8 - [Release notes](https://github.com/ansys/pymechanical/releases) - [Changelog](https://github.com/ansys/pymechanical/blob/main/CHANGELOG.md) - [Commits](https://github.com/ansys/pymechanical/compare/v0.11.7...v0.11.8) Updates `hypothesis` from 6.115.3 to 6.115.5 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.115.3...hypothesis-python-6.115.5) --- updated-dependencies: - dependency-name: ansys-mechanical-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 68 ++++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/poetry.lock b/poetry.lock index acf59f8620..6b958da2c0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -392,18 +392,18 @@ tests = ["ansys-mapdl-core (==0.68.5)", "numpy (==2.1.2)", "pyansys-tools-report [[package]] name = "ansys-mechanical-core" -version = "0.11.7" +version = "0.11.8" description = "A python wrapper for Ansys Mechanical" optional = false -python-versions = "<4.0,>=3.9" +python-versions = "<4.0,>=3.10" files = [ - {file = "ansys_mechanical_core-0.11.7-py3-none-any.whl", hash = "sha256:e13a98a87648ea80ad3c9d3901504d05c3500b359b0f88dfb06bc066e97783f1"}, - {file = "ansys_mechanical_core-0.11.7.tar.gz", hash = "sha256:6c54a5b4504ff3ffa3823e1c8e53c91b6d2f182c6fe4f6fd553f68a3e882f940"}, + {file = "ansys_mechanical_core-0.11.8-py3-none-any.whl", hash = "sha256:fc9cbce588f6ff9d8c877f4c83d59b90cbf36df75d8c994320bffd2d335a3cff"}, + {file = "ansys_mechanical_core-0.11.8.tar.gz", hash = "sha256:607f3d3fb89c13594d81be6692196727f723005aff112f51c4b26a233d3a8277"}, ] [package.dependencies] ansys-api-mechanical = "0.1.2" -ansys-mechanical-env = "0.1.7" +ansys-mechanical-env = "0.1.8" ansys-platform-instancemanagement = ">=1.0.1" ansys-pythonnet = ">=3.1.0rc2" ansys-tools-path = ">=0.3.1" @@ -412,22 +412,23 @@ click = ">=8.1.3" clr-loader = "0.2.6" grpcio = ">=1.30.0" protobuf = ">=3.12.2,<6" +psutil = "6.0.0" tqdm = ">=4.45.0" [package.extras] -doc = ["ansys-sphinx-theme[autoapi] (==1.0.7)", "grpcio (==1.66.0)", "imageio (==2.35.1)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.0)", "numpydoc (==1.8.0)", "pandas (==2.2.2)", "panel (==1.4.5)", "plotly (==5.23.0)", "pypandoc (==1.13)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.0.2)", "sphinx-autobuild (==2024.4.16)", "sphinx-autodoc-typehints (==2.2.3)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.17.1)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] -tests = ["pytest (==8.3.2)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.0)"] +doc = ["ansys-sphinx-theme[autoapi] (==1.1.4)", "grpcio (==1.66.2)", "imageio (==2.36.0)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "panel (==1.5.2)", "plotly (==5.24.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.1.3)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.5.0)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] +tests = ["psutil (==6.0.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.2)"] viz = ["ansys-tools-visualization-interface (>=0.2.6)", "usd-core (==24.8)"] [[package]] name = "ansys-mechanical-env" -version = "0.1.7" +version = "0.1.8" description = "A python wrapper for loading environment variables when using PyMechanical embedded instances in Linux." optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" files = [ - {file = "ansys_mechanical_env-0.1.7-py3-none-any.whl", hash = "sha256:03004cc200a9c8e43fdc04b8879436f8089d4391daf98d7fac429bc997b62d6d"}, - {file = "ansys_mechanical_env-0.1.7.tar.gz", hash = "sha256:29a4ce796cf3719828f6af4fa7d1097333cb12d1b10b6ab199efe0f7ec8fc346"}, + {file = "ansys_mechanical_env-0.1.8-py3-none-any.whl", hash = "sha256:4746c83924cba91137ffd367712a033abf2a8ccc66e7e02b76c1a3a6a80b4bf9"}, + {file = "ansys_mechanical_env-0.1.8.tar.gz", hash = "sha256:5286202292f6aeb3bf2278003059dbcd64774db2decb16a8cf68d15affef51d6"}, ] [package.dependencies] @@ -1886,13 +1887,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.115.3" +version = "6.115.5" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.115.3-py3-none-any.whl", hash = "sha256:d2770b0db08ad666fe6ff36027910039ab681084d13bcf9c057449c2e27099c4"}, - {file = "hypothesis-6.115.3.tar.gz", hash = "sha256:d4efc8c7371bd4ec906d2777f1f18fee5539e47b3d7c7cdc93d1026ad35d9b33"}, + {file = "hypothesis-6.115.5-py3-none-any.whl", hash = "sha256:b7733459ae9a93020fac3b91b41473c9b85e975139a152a70d88f3a5caa3fa3f"}, + {file = "hypothesis-6.115.5.tar.gz", hash = "sha256:4768c5fb426b305462ed31032d6e216a31daaefb1dc3134fdf2795b7961d7cb3"}, ] [package.dependencies] @@ -3508,33 +3509,32 @@ files = [ [[package]] name = "psutil" -version = "6.1.0" +version = "6.0.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, - {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, - {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, - {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, - {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, - {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, - {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, - {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, ] [package.extras] -dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] -test = ["pytest", "pytest-xdist", "setuptools"] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "ptyprocess" From 398b77e5984a2cd8a79a094ec172c509578b65a3 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 28 Oct 2024 10:44:18 +0100 Subject: [PATCH 28/96] Add section cut (#630) Add exposure for the `SectionCut` object. --- doc/source/api/enum_types.rst | 3 + doc/source/api/tree_objects.rst | 1 + doc/source/index.rst | 2 +- src/ansys/acp/core/__init__.py | 8 + src/ansys/acp/core/_tree_objects/__init__.py | 8 + src/ansys/acp/core/_tree_objects/enums.py | 25 ++ src/ansys/acp/core/_tree_objects/model.py | 10 + .../acp/core/_tree_objects/section_cut.py | 227 ++++++++++++++++++ tests/unittests/test_section_cut.py | 111 +++++++++ 9 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 src/ansys/acp/core/_tree_objects/section_cut.py create mode 100644 tests/unittests/test_section_cut.py diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 1ba5f80310..3762cbec43 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -18,9 +18,11 @@ Enumeration data types EdgeSetType EdgeSetType ElementalDataType + ExtrusionType FeFormat GeometricalRuleType IgnorableEntity + IntersectionType LinkedObjectHandling LookUpTable3DInterpolationAlgorithm LookUpTableColumnValueType @@ -31,6 +33,7 @@ Enumeration data types PlyType RosetteSelectionMethod RosetteType + SectionCutType SensorType StatusType SymmetryType diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index d95ad469ac..bb0435530a 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -30,6 +30,7 @@ ACP objects ParallelSelectionRule ProductionPly Rosette + SectionCut Sensor SphericalSelectionRule Stackup diff --git a/doc/source/index.rst b/doc/source/index.rst index 3cfe23eaf0..799731da1f 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -78,4 +78,4 @@ Limitations * Only shell workflows are supported, solid models can not yet be defined using PyACP * FieldDefinitions for variable material properties are not supported -* Section cuts are not supported +* Section cuts cannot be visualized diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 2401fe59b6..e760e364cc 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -69,6 +69,7 @@ ElementSet, ElementSetElementalData, ElementSetNodalData, + ExtrusionType, Fabric, FabricWithAngle, FeFormat, @@ -78,6 +79,7 @@ GeometricalSelectionRuleNodalData, IgnorableEntity, InterfaceLayer, + IntersectionType, Lamina, LinkedSelectionRule, LookUpTable1D, @@ -114,6 +116,8 @@ RosetteSelectionMethod, RosetteType, ScalarData, + SectionCut, + SectionCutType, Sensor, SensorType, SphericalSelectionRule, @@ -181,6 +185,7 @@ "ElementSetElementalData", "ElementSetNodalData", "example_helpers", + "ExtrusionType", "Fabric", "FabricWithAngle", "FeFormat", @@ -194,6 +199,7 @@ "get_model_tree", "IgnorableEntity", "InterfaceLayer", + "IntersectionType", "Lamina", "launch_acp", "LaunchMode", @@ -236,6 +242,8 @@ "RosetteSelectionMethod", "RosetteType", "ScalarData", + "SectionCut", + "SectionCutType", "Sensor", "SensorType", "SphericalSelectionRule", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 0b86c423bb..0ffbecaad4 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -53,7 +53,9 @@ DropoffMaterialType, EdgeSetType, ElementalDataType, + ExtrusionType, GeometricalRuleType, + IntersectionType, LookUpTable3DInterpolationAlgorithm, LookUpTableColumnValueType, NodalDataType, @@ -63,6 +65,7 @@ PlyType, RosetteSelectionMethod, RosetteType, + SectionCutType, SensorType, StatusType, SymmetryType, @@ -99,6 +102,7 @@ ) from .production_ply import ProductionPly, ProductionPlyElementalData, ProductionPlyNodalData from .rosette import Rosette +from .section_cut import SectionCut from .sensor import Sensor from .spherical_selection_rule import ( SphericalSelectionRule, @@ -149,6 +153,7 @@ "ElementSet", "ElementSetElementalData", "ElementSetNodalData", + "ExtrusionType", "Fabric", "FabricWithAngle", "FeFormat", @@ -160,6 +165,7 @@ "IgnorableEntity", "InterfaceLayer", "InterpolationOptions", + "IntersectionType", "Lamina", "LinkedSelectionRule", "LookUpTable1D", @@ -197,6 +203,8 @@ "RosetteSelectionMethod", "RosetteType", "ScalarData", + "SectionCut", + "SectionCutType", "Sensor", "SensorType", "SphericalSelectionRule", diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index 9b7963fb36..fd9a089140 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -33,6 +33,7 @@ modeling_ply_pb2, ply_material_pb2, rosette_pb2, + section_cut_pb2, sensor_pb2, unit_system_pb2, virtual_geometry_pb2, @@ -51,7 +52,9 @@ "DropoffMaterialType", "EdgeSetType", "ElementalDataType", + "ExtrusionType", "GeometricalRuleType", + "IntersectionType", "LookUpTable3DInterpolationAlgorithm", "LookUpTableColumnValueType", "NodalDataType", @@ -61,6 +64,7 @@ "PlyType", "RosetteSelectionMethod", "RosetteType", + "SectionCutType", "SensorType", "StatusType", "SymmetryType", @@ -368,3 +372,24 @@ enum_types_pb2.FileFormat.STL, ), ) + +(ExtrusionType, extrusion_type_to_pb, extrusion_type_from_pb) = wrap_to_string_enum( + "ExtrusionType", + section_cut_pb2.ExtrusionType, + module=__name__, + doc="Extrusion method used in a section cut.", +) + +(SectionCutType, section_cut_type_to_pb, section_cut_type_from_pb) = wrap_to_string_enum( + "SectionCutType", + section_cut_pb2.SectionCutType, + module=__name__, + doc="Determines whether the section cut is extruded by modeling ply, production ply, or analysis ply.", +) + +(IntersectionType, intersection_type_to_pb, intersection_type_from_pb) = wrap_to_string_enum( + "IntersectionType", + section_cut_pb2.IntersectionType, + module=__name__, + doc="Determines how the intersection is computed for wireframe section cuts.", +) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index b083ecdf68..d4ba2aaa8f 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -55,6 +55,7 @@ parallel_selection_rule_pb2_grpc, ply_geometry_export_pb2, rosette_pb2_grpc, + section_cut_pb2_grpc, sensor_pb2_grpc, spherical_selection_rule_pb2_grpc, stackup_pb2_grpc, @@ -120,6 +121,7 @@ from .oriented_selection_set import OrientedSelectionSet from .parallel_selection_rule import ParallelSelectionRule from .rosette import Rosette +from .section_cut import SectionCut from .sensor import Sensor from .spherical_selection_rule import SphericalSelectionRule from .stackup import Stackup @@ -708,6 +710,14 @@ def export_modeling_ply_geometries( ModelingGroup, modeling_group_pb2_grpc.ObjectServiceStub ) + create_section_cut = define_create_method( + SectionCut, + func_name="create_section_cut", + parent_class_name="Model", + module_name=__module__, + ) + section_cuts = define_mutable_mapping(SectionCut, section_cut_pb2_grpc.ObjectServiceStub) + create_sensor = define_create_method( Sensor, func_name="create_sensor", parent_class_name="Model", module_name=__module__ ) diff --git a/src/ansys/acp/core/_tree_objects/section_cut.py b/src/ansys/acp/core/_tree_objects/section_cut.py new file mode 100644 index 0000000000..254c2903b2 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/section_cut.py @@ -0,0 +1,227 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Sequence + +from ansys.api.acp.v0 import section_cut_pb2, section_cut_pb2_grpc + +from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array +from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from ._grpc_helpers.linked_object_list import define_linked_object_list +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .element_set import ElementSet +from .enums import ( + ExtrusionType, + IntersectionType, + SectionCutType, + extrusion_type_from_pb, + extrusion_type_to_pb, + intersection_type_from_pb, + intersection_type_to_pb, + section_cut_type_from_pb, + section_cut_type_to_pb, + status_type_from_pb, +) +from .object_registry import register + +__all__ = ["SectionCut"] + + +@mark_grpc_properties +@register +class SectionCut(CreatableTreeObject, IdTreeObject): + r"""Instantiate a Section Cut. + + Parameters + ---------- + name : + Name of the section cut. + active : + Inactive section cuts are not evaluated. + origin : + Defines the origin of the section cut plane. + normal : + Defines the normal vector of the section cut plane. + in_plane_reference_direction1 : + Defines the in-plane transverse direction of the beam. Used for the surface + section cut and section cut measures. + scope_entire_model : + If True, the section cut is applied to the entire model. Otherwise, the + section cut is applied only to the element sets specified in + ``scope_element_sets``. + scope_element_sets : + The element sets to which the section cut is applied. Used only if + ``scope_entire_model`` is False. + extrusion_type : + Determines the extrusion method used to create the section cut. + scale_factor : + Scale factor applied to the ply thicknesses. + core_scale_factor : + Scale factor applied to the core thicknesses. + section_cut_type : + Determines whether the section cut is extruded by modeling ply, production + ply, or analysis ply. + intersection_type : + Determines the method used to compute a wire frame section cut. Used only + if ``extrusion_type`` is ``ExtrusionType.WIRE_FRAME``. + use_default_tolerance : + If True, the segment tolerance is computed as 0.1\% of the averaged element size. + Otherwise, the ``tolerance`` value is used. + Used only if ``extrusion_type`` is ``ExtrusionType.SURFACE_NORMAL`` or + ``ExtrusionType.SURFACE_SWEEP_BASED``. + tolerance : + Defines the minimum length of the segments. Segments shorter than this value + are merged. + Used only if ``extrusion_type`` is ``ExtrusionType.SURFACE_NORMAL`` or + ``ExtrusionType.SURFACE_SWEEP_BASED``, and ``use_default_tolerance`` is + False. + use_default_interpolation_settings : + If True, default interpolation settings are used by the sweep-based extrusion. + Used only if ``extrusion_type`` is ``ExtrusionType.SURFACE_SWEEP_BASED``. + search_radius : + Search radius of the interpolation algorithm used in the sweep-based extrusion. + Used only if ``extrusion_type`` is ``ExtrusionType.SURFACE_SWEEP_BASED`` and + ``use_default_interpolation_settings`` is False. + number_of_interpolation_points : + Number of interpolation points of the interpolation algorithm used in the + sweep-based extrusion. + Used only if ``extrusion_type`` is ``ExtrusionType.SURFACE_SWEEP_BASED`` and + ``use_default_interpolation_settings`` is False. + + """ + + __slots__ = () + + _COLLECTION_LABEL = "section_cuts" + _OBJECT_INFO_TYPE = section_cut_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = section_cut_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "SectionCut", + active: bool = True, + origin: tuple[float, float, float] = (0.0, 0.0, 0.0), + normal: tuple[float, float, float] = (0.0, 0.0, 1.0), + in_plane_reference_direction1: tuple[float, float, float] = (1.0, 0.0, 0.0), + scope_entire_model: bool = True, + scope_element_sets: Sequence[ElementSet] = tuple(), + extrusion_type: ExtrusionType = ExtrusionType.WIRE_FRAME, + scale_factor: float = 1.0, + core_scale_factor: float = 1.0, + section_cut_type: SectionCutType = SectionCutType.MODELING_PLY_WISE, + intersection_type: IntersectionType = IntersectionType.NORMAL_TO_SURFACE, + use_default_tolerance: bool = True, + tolerance: float = 0.0, + use_default_interpolation_settings: bool = True, + search_radius: float = 0.0, + number_of_interpolation_points: int = 1, + ) -> None: + super().__init__(name=name) + self.active = active + self.origin = origin + self.normal = normal + self.in_plane_reference_direction1 = in_plane_reference_direction1 + self.scope_entire_model = scope_entire_model + self.scope_element_sets = scope_element_sets + self.extrusion_type = extrusion_type + self.scale_factor = scale_factor + self.core_scale_factor = core_scale_factor + self.section_cut_type = section_cut_type + self.intersection_type = intersection_type + self.use_default_tolerance = use_default_tolerance + self.tolerance = tolerance + self.use_default_interpolation_settings = use_default_interpolation_settings + self.search_radius = search_radius + self.number_of_interpolation_points = number_of_interpolation_points + + def _create_stub(self) -> section_cut_pb2_grpc.ObjectServiceStub: + return section_cut_pb2_grpc.ObjectServiceStub(self._channel) + + # general properties + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + locked: ReadOnlyProperty[bool] = grpc_data_property_read_only("properties.locked") + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + + # position properties + origin = grpc_data_property( + "properties.origin", from_protobuf=to_tuple_from_1D_array, to_protobuf=to_1D_double_array + ) + normal = grpc_data_property( + "properties.normal", from_protobuf=to_tuple_from_1D_array, to_protobuf=to_1D_double_array + ) + in_plane_reference_direction1 = grpc_data_property( + "properties.in_plane_reference_direction1", + from_protobuf=to_tuple_from_1D_array, + to_protobuf=to_1D_double_array, + ) + + # scoping properties + scope_entire_model: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.scope_entire_model" + ) + scope_element_sets = define_linked_object_list("properties.scope_element_sets", ElementSet) + + # extrusion properties + extrusion_type = grpc_data_property( + "properties.extrusion_type", + from_protobuf=extrusion_type_from_pb, + to_protobuf=extrusion_type_to_pb, + ) + scale_factor: ReadWriteProperty[float, float] = grpc_data_property("properties.scale_factor") + core_scale_factor: ReadWriteProperty[float, float] = grpc_data_property( + "properties.core_scale_factor" + ) + section_cut_type = grpc_data_property( + "properties.section_cut_type", + from_protobuf=section_cut_type_from_pb, + to_protobuf=section_cut_type_to_pb, + ) + + # wireframe properties + intersection_type = grpc_data_property( + "properties.intersection_type", + from_protobuf=intersection_type_from_pb, + to_protobuf=intersection_type_to_pb, + ) + + # surface properties - general + use_default_tolerance: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.use_default_tolerance" + ) + tolerance: ReadWriteProperty[float, float] = grpc_data_property("properties.tolerance") + # surface properties - sweep-based + use_default_interpolation_settings: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.use_default_interpolation_settings" + ) + search_radius: ReadWriteProperty[float, float] = grpc_data_property("properties.search_radius") + number_of_interpolation_points: ReadWriteProperty[int, int] = grpc_data_property( + "properties.number_of_interpolation_points" + ) diff --git a/tests/unittests/test_section_cut.py b/tests/unittests/test_section_cut.py new file mode 100644 index 0000000000..864585ef3a --- /dev/null +++ b/tests/unittests/test_section_cut.py @@ -0,0 +1,111 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import math + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ExtrusionType, IntersectionType, SectionCut, SectionCutType + +from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(SectionCut._SUPPORTED_SINCE): + pytest.skip("InterfaceLayer is not supported on this version of the server.") + + +@pytest.fixture +def parent_object(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_section_cut() + + +class TestSectionCut(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "section_cuts" + CREATE_METHOD_NAME = "create_section_cut" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "locked": False, + "active": True, + "origin": (0, 0, 0), + "normal": (0, 0, 1), + "in_plane_reference_direction1": (1, 0, 0), + "scope_entire_model": True, + "scope_element_sets": [], + "extrusion_type": ExtrusionType.WIRE_FRAME, + "scale_factor": 1.0, + "core_scale_factor": 1.0, + "section_cut_type": SectionCutType.MODELING_PLY_WISE, + "intersection_type": IntersectionType.NORMAL_TO_SURFACE, + "use_default_tolerance": True, + "tolerance": 0.0, + "use_default_interpolation_settings": True, + "search_radius": 0.0, + "number_of_interpolation_points": 1, + } + + @staticmethod + @pytest.fixture + def object_properties(parent_object): + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_name"), + ("active", False), + ("origin", (0.1, 0.2, 0.3)), + ("normal", (0, 1.0 / math.sqrt(2), 1.0 / math.sqrt(2))), + ("in_plane_reference_direction1", (math.sqrt(1 / 3), math.sqrt(2 / 3), 0)), + ("scope_entire_model", False), + ( + "scope_element_sets", + [parent_object.create_element_set(), parent_object.create_element_set()], + ), + ("extrusion_type", ExtrusionType.SURFACE_NORMAL), + ("extrusion_type", ExtrusionType.SURFACE_SWEEP_BASED), + ("scale_factor", 1.5), + ("core_scale_factor", 12.3), + ("section_cut_type", SectionCutType.PRODUCTION_PLY_WISE), + ("section_cut_type", SectionCutType.ANALYSIS_PLY_WISE), + ("intersection_type", IntersectionType.IN_PLANE), + ("use_default_tolerance", False), + ("tolerance", 0.6), + ("use_default_interpolation_settings", False), + ("search_radius", 12.3), + ("number_of_interpolation_points", 5), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ("locked", True), + ], + ) From 84ec13f25f6203ccd590a288a3b692ba37873768 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 28 Oct 2024 11:06:11 +0100 Subject: [PATCH 29/96] Add sampling point (#629) Add exposure for the `SamplingPoint` object. Closes #594. --- doc/source/api/tree_objects.rst | 1 + doc/source/index.rst | 1 + src/ansys/acp/core/__init__.py | 2 + src/ansys/acp/core/_tree_objects/__init__.py | 2 + src/ansys/acp/core/_tree_objects/model.py | 12 ++ .../acp/core/_tree_objects/sampling_point.py | 126 ++++++++++++++++++ tests/unittests/test_sampling_point.py | 84 ++++++++++++ 7 files changed, 228 insertions(+) create mode 100644 src/ansys/acp/core/_tree_objects/sampling_point.py create mode 100644 tests/unittests/test_sampling_point.py diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index bb0435530a..f4130a3755 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -30,6 +30,7 @@ ACP objects ParallelSelectionRule ProductionPly Rosette + SamplingPoint SectionCut Sensor SphericalSelectionRule diff --git a/doc/source/index.rst b/doc/source/index.rst index 799731da1f..3db3aeed67 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -79,3 +79,4 @@ Limitations * Only shell workflows are supported, solid models can not yet be defined using PyACP * FieldDefinitions for variable material properties are not supported * Section cuts cannot be visualized +* Sampling point analysis data is not available diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index e760e364cc..fcfb28fcf6 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -115,6 +115,7 @@ Rosette, RosetteSelectionMethod, RosetteType, + SamplingPoint, ScalarData, SectionCut, SectionCutType, @@ -241,6 +242,7 @@ "Rosette", "RosetteSelectionMethod", "RosetteType", + "SamplingPoint", "ScalarData", "SectionCut", "SectionCutType", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 0ffbecaad4..f76b6241b7 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -102,6 +102,7 @@ ) from .production_ply import ProductionPly, ProductionPlyElementalData, ProductionPlyNodalData from .rosette import Rosette +from .sampling_point import SamplingPoint from .section_cut import SectionCut from .sensor import Sensor from .spherical_selection_rule import ( @@ -202,6 +203,7 @@ "Rosette", "RosetteSelectionMethod", "RosetteType", + "SamplingPoint", "ScalarData", "SectionCut", "SectionCutType", diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index d4ba2aaa8f..ceda10cce4 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -55,6 +55,7 @@ parallel_selection_rule_pb2_grpc, ply_geometry_export_pb2, rosette_pb2_grpc, + sampling_point_pb2_grpc, section_cut_pb2_grpc, sensor_pb2_grpc, spherical_selection_rule_pb2_grpc, @@ -121,6 +122,7 @@ from .oriented_selection_set import OrientedSelectionSet from .parallel_selection_rule import ParallelSelectionRule from .rosette import Rosette +from .sampling_point import SamplingPoint from .section_cut import SectionCut from .sensor import Sensor from .spherical_selection_rule import SphericalSelectionRule @@ -710,6 +712,16 @@ def export_modeling_ply_geometries( ModelingGroup, modeling_group_pb2_grpc.ObjectServiceStub ) + create_sampling_point = define_create_method( + SamplingPoint, + func_name="create_sampling_point", + parent_class_name="Model", + module_name=__module__, + ) + sampling_points = define_mutable_mapping( + SamplingPoint, sampling_point_pb2_grpc.ObjectServiceStub + ) + create_section_cut = define_create_method( SectionCut, func_name="create_section_cut", diff --git a/src/ansys/acp/core/_tree_objects/sampling_point.py b/src/ansys/acp/core/_tree_objects/sampling_point.py new file mode 100644 index 0000000000..b60e32c814 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/sampling_point.py @@ -0,0 +1,126 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable + +from ansys.api.acp.v0 import sampling_point_pb2, sampling_point_pb2_grpc + +from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array +from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .enums import status_type_from_pb +from .object_registry import register +from .rosette import Rosette + +__all__ = ["SamplingPoint"] + + +@mark_grpc_properties +@register +class SamplingPoint(CreatableTreeObject, IdTreeObject): + """Instantiate a Sampling Point. + + Parameters + ---------- + name : + Name of the sampling point. + point : + Coordinates of the sampling point. + direction : + Direction of the sampling point. + use_default_reference_direction : + Whether to use the element coordinate system when computing the laminate + properties (CLT). + rosette : + Rosette defining the coordinate system used when computing the laminate + properties (CLT). Only used when ``use_default_reference_direction`` is False. + offset_is_middle : + Activate this option to offset the reference surface to the mid-plane of the + laminate for the computation of the ABD matrices and laminate properties (CLT). + consider_coupling_effect : + Whether the computation of the laminate engineering constants should consider + the coupling effect if the B-Matrix of the ABD-Matrix is not zero. Computation + of the ABD matrices is not affected by this parameter. + + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "sampling_points" + _OBJECT_INFO_TYPE = sampling_point_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = sampling_point_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "SamplingPoint", + point: tuple[float, float, float] = (0.0, 0.0, 0.0), + direction: tuple[float, float, float] = (0.0, 0.0, 0.0), + use_default_reference_direction: bool = True, + rosette: Rosette | None = None, + offset_is_middle: bool = True, + consider_coupling_effect: bool = True, + ): + super().__init__(name=name) + + self.point = point + self.direction = direction + self.use_default_reference_direction = use_default_reference_direction + self.rosette = rosette + self.offset_is_middle = offset_is_middle + self.consider_coupling_effect = consider_coupling_effect + + def _create_stub(self) -> sampling_point_pb2_grpc.ObjectServiceStub: + return sampling_point_pb2_grpc.ObjectServiceStub(self._channel) + + locked: ReadOnlyProperty[bool] = grpc_data_property_read_only("properties.locked") + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + point = grpc_data_property( + "properties.point", from_protobuf=to_tuple_from_1D_array, to_protobuf=to_1D_double_array + ) + direction = grpc_data_property( + "properties.direction", from_protobuf=to_tuple_from_1D_array, to_protobuf=to_1D_double_array + ) + use_default_reference_direction: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.use_default_reference_direction" + ) + rosette = grpc_link_property("properties.rosette", allowed_types=Rosette) + reference_direction = grpc_data_property_read_only( + "properties.reference_direction", + from_protobuf=to_tuple_from_1D_array, + doc="Local x-direction used in the computation of laminate properties (CLT).", + ) + offset_is_middle: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.offset_is_middle" + ) + consider_coupling_effect: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.consider_coupling_effect" + ) diff --git a/tests/unittests/test_sampling_point.py b/tests/unittests/test_sampling_point.py new file mode 100644 index 0000000000..34a24c635c --- /dev/null +++ b/tests/unittests/test_sampling_point.py @@ -0,0 +1,84 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import SamplingPoint + +from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(SamplingPoint._SUPPORTED_SINCE): + pytest.skip("InterfaceLayer is not supported on this version of the server.") + + +@pytest.fixture +def parent_object(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_sampling_point() + + +class TestSamplingPoint(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "sampling_points" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "point": (0.0, 0.0, 0.0), + "direction": (0.0, 0.0, 0.0), + "use_default_reference_direction": True, + "rosette": None, + "offset_is_middle": True, + "consider_coupling_effect": True, + } + + CREATE_METHOD_NAME = "create_sampling_point" + + @staticmethod + @pytest.fixture + def object_properties(parent_object): + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_name"), + ("point", (0.1, 0.2, 0.3)), + ("direction", (0.4, 0.5, 0.6)), + ("use_default_reference_direction", False), + ("rosette", parent_object.create_rosette()), + ("offset_is_middle", False), + ("consider_coupling_effect", False), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ("reference_direction", (0.1, 0.2, 0.3)), + ], + ) From e54930495a9a65c1cf343195a6c8ab407fe2d138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:03:32 +0100 Subject: [PATCH 30/96] Fabric: complete material handling of solid models (#631) * complete material handling for solid models to fabric * add supported since handling to the new properties --------- Co-authored-by: Dominik Gresch --- .../_grpc_helpers/property_helper.py | 30 +++- src/ansys/acp/core/_tree_objects/fabric.py | 22 ++- tests/unittests/test_fabric.py | 128 +++++++++++++----- 3 files changed, 139 insertions(+), 41 deletions(-) diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py index bcef7ddfba..52435ff4aa 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py @@ -116,9 +116,19 @@ def mark_grpc_properties(cls: T) -> T: return cls -def grpc_linked_object_getter(name: str) -> Callable[[Readable], Any]: +def grpc_linked_object_getter( + name: str, readable_since: str | None = None +) -> Callable[[Readable], Any]: """Create a getter method which obtains the linked server object.""" + @supported_since_decorator( + readable_since, + # The default error message uses 'inner' as the method name, which is confusing + err_msg_tpl=( + f"The property '{name.split('.')[-1]}' is only readable since version {{required_version}} " + f"of the ACP gRPC server. The current server version is {{server_version}}." + ), + ) def inner(self: Readable) -> CreatableFromResourcePath | None: if not self._is_stored: raise RuntimeError(f"Cannot get linked object '{name}' from unstored object") @@ -170,10 +180,10 @@ def inner(self: Readable) -> Any: def grpc_linked_object_setter( - name: str, to_protobuf: _TO_PROTOBUF_T[Readable | None] + name: str, to_protobuf: _TO_PROTOBUF_T[Readable | None], writable_since: str | None = None ) -> Callable[[Editable, Readable | None], None]: """Create a setter method which updates the linked object via the gRPC Put endpoint.""" - func = grpc_data_setter(name, to_protobuf) + func = grpc_data_setter(name=name, to_protobuf=to_protobuf, supported_since=writable_since) def inner(self: Editable, value: Readable | None) -> None: if value is not None and not value._is_stored: @@ -361,6 +371,8 @@ def grpc_link_property( *, doc: str | None = None, allowed_types: type[GrpcObjectBase] | tuple[type[GrpcObjectBase], ...], + readable_since: str | None = None, + writable_since: str | None = None, ) -> Any: """Define a gRPC-backed property linking to another object. @@ -376,6 +388,10 @@ def grpc_link_property( allowed_types : Types which are allowed to be set on the property. An error will be raised if an object of a different type is set. + readable_since : + Version since which the property is supported for reading. + writable_since : + Version since which the property is supported for setting. """ def to_protobuf(obj: Readable | None) -> ResourcePath: @@ -390,9 +406,13 @@ def to_protobuf(obj: Readable | None) -> ResourcePath: return obj._resource_path return _wrap_doc( - _exposed_grpc_property(grpc_linked_object_getter(name)).setter( + _exposed_grpc_property( + grpc_linked_object_getter(name=name, readable_since=readable_since) + ).setter( # Resource path represents an object that is not set as an empty string - grpc_linked_object_setter(name=name, to_protobuf=to_protobuf) + grpc_linked_object_setter( + name=name, to_protobuf=to_protobuf, writable_since=writable_since + ) ), doc=doc, ) diff --git a/src/ansys/acp/core/_tree_objects/fabric.py b/src/ansys/acp/core/_tree_objects/fabric.py index be75c5f56b..99e04b8791 100644 --- a/src/ansys/acp/core/_tree_objects/fabric.py +++ b/src/ansys/acp/core/_tree_objects/fabric.py @@ -71,8 +71,12 @@ class Fabric(CreatableTreeObject, IdTreeObject): Enable this option that the failure computation skips all plies made of this fabric. drop_off_material_handling : Defines the material of drop-off elements in the solid model extrusion. + drop_off_material : + Specify the material of drop-off elements in the solid model. cut_off_material_handling : Defines the material of cut-off elements in solid models if cut-off geometries are active. + cut_off_material : + Define the cut-off material if a ply with this material is shaped by a cut-off geometry. draping_material_model : Specifies the draping model of the fabric. draping_ud_coefficient : @@ -97,7 +101,9 @@ def __init__( area_price: float = 0.0, ignore_for_postprocessing: bool = False, drop_off_material_handling: DropoffMaterialType = "global", + drop_off_material: Material | None = None, cut_off_material_handling: CutoffMaterialType = "computed", + cut_off_material: Material | None = None, draping_material_model: DrapingMaterialType = "woven", draping_ud_coefficient: float = 0.0, ): @@ -108,7 +114,9 @@ def __init__( self.area_price = area_price self.ignore_for_postprocessing = ignore_for_postprocessing self.drop_off_material_handling = DropoffMaterialType(drop_off_material_handling) + self.drop_off_material = drop_off_material self.cut_off_material_handling = CutoffMaterialType(cut_off_material_handling) + self.cut_off_material = cut_off_material self.draping_material_model = DrapingMaterialType(draping_material_model) self.draping_ud_coefficient = draping_ud_coefficient @@ -130,17 +138,29 @@ def _create_stub(self) -> fabric_pb2_grpc.ObjectServiceStub: from_protobuf=drop_off_material_type_from_pb, to_protobuf=drop_off_material_type_to_pb, ) + drop_off_material = grpc_link_property( + "properties.drop_off_material", + allowed_types=Material, + readable_since="25.1", + writable_since="25.1", + ) cut_off_material_handling = grpc_data_property( "properties.cut_off_material_handling", from_protobuf=cut_off_material_type_from_pb, to_protobuf=cut_off_material_type_to_pb, ) + cut_off_material = grpc_link_property( + "properties.cut_off_material", + allowed_types=Material, + readable_since="25.1", + writable_since="25.1", + ) + draping_material_model = grpc_data_property( "properties.draping_material_model", from_protobuf=draping_material_type_from_pb, to_protobuf=draping_material_type_to_pb, ) - draping_ud_coefficient: ReadWriteProperty[float, float] = grpc_data_property( "properties.draping_ud_coefficient" ) diff --git a/tests/unittests/test_fabric.py b/tests/unittests/test_fabric.py index d3e9d6ffe7..1e53dd7bb7 100644 --- a/tests/unittests/test_fabric.py +++ b/tests/unittests/test_fabric.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from packaging.version import parse as parse_version import pytest from ansys.acp.core import CutoffMaterialType, DrapingMaterialType, DropoffMaterialType @@ -43,44 +44,101 @@ class TestFabric(NoLockedMixin, TreeObjectTester): @staticmethod @pytest.fixture - def default_properties(): - return { - "status": "NOTUPTODATE", - "thickness": 0.0, - "area_price": 0.0, - "ignore_for_postprocessing": False, - "drop_off_material_handling": DropoffMaterialType.GLOBAL, - "cut_off_material_handling": CutoffMaterialType.COMPUTED, - "draping_material_model": DrapingMaterialType.WOVEN, - "draping_ud_coefficient": 0.0, - "material": None, - } + def default_properties(acp_instance): + if parse_version(acp_instance.server_version) < parse_version("25.1"): + return { + "status": "NOTUPTODATE", + "thickness": 0.0, + "area_price": 0.0, + "ignore_for_postprocessing": False, + "drop_off_material_handling": DropoffMaterialType.GLOBAL, + "cut_off_material_handling": CutoffMaterialType.COMPUTED, + "draping_material_model": DrapingMaterialType.WOVEN, + "draping_ud_coefficient": 0.0, + "material": None, + } + else: + return { + "status": "NOTUPTODATE", + "thickness": 0.0, + "area_price": 0.0, + "ignore_for_postprocessing": False, + "drop_off_material_handling": DropoffMaterialType.GLOBAL, + "drop_off_material": None, + "cut_off_material_handling": CutoffMaterialType.COMPUTED, + "cut_off_material": None, + "draping_material_model": DrapingMaterialType.WOVEN, + "draping_ud_coefficient": 0.0, + "material": None, + } CREATE_METHOD_NAME = "create_fabric" @staticmethod @pytest.fixture - def object_properties(parent_object): + def object_properties(parent_object, acp_instance): model = parent_object - material = model.create_material() - return ObjectPropertiesToTest( - read_write=[ - ("name", "Fabric name"), - ("thickness", 1e-6), - ("area_price", 5.98), - ("ignore_for_postprocessing", True), - ("drop_off_material_handling", DropoffMaterialType.CUSTOM), - ("cut_off_material_handling", CutoffMaterialType.GLOBAL), - ("draping_material_model", DrapingMaterialType.UD), - ("draping_ud_coefficient", 0.55), - ("material", material), - ("material", None), - ("material", material), - ], - read_only=[ - ("id", "some_id"), - ("status", "UPTODATE"), - ("area_weight", 0.0), - # ("draping_ud_coefficient", 4.32), # TODO: enable this check, see backend issue #778698 - ], - ) + material = model.create_material(name="Material") + cut_off_material = model.create_material(name="Cut-off Material") + drop_off_material = model.create_material(name="Drop-off Material") + if parse_version(acp_instance.server_version) < parse_version("25.1"): + return ObjectPropertiesToTest( + read_write=[ + ("name", "Fabric name"), + ("thickness", 1e-6), + ("area_price", 5.98), + ("ignore_for_postprocessing", True), + ("drop_off_material_handling", DropoffMaterialType.GLOBAL), + ("cut_off_material_handling", CutoffMaterialType.COMPUTED), + ("draping_material_model", DrapingMaterialType.UD), + ("draping_ud_coefficient", 0.55), + ("material", material), + ("material", None), + ("material", material), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ("area_weight", 0.0), + # ("draping_ud_coefficient", 4.32), # TODO: enable this check, see backend issue #778698 + ], + ) + else: + return ObjectPropertiesToTest( + read_write=[ + ("name", "Fabric name"), + ("thickness", 1e-6), + ("area_price", 5.98), + ("ignore_for_postprocessing", True), + ("drop_off_material_handling", DropoffMaterialType.CUSTOM), + ("drop_off_material", drop_off_material), + ("cut_off_material_handling", CutoffMaterialType.CUSTOM), + ("cut_off_material", cut_off_material), + ("draping_material_model", DrapingMaterialType.UD), + ("draping_ud_coefficient", 0.55), + ("material", material), + ("material", None), + ("material", material), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ("area_weight", 0.0), + ], + ) + + +@pytest.mark.parametrize("material_type", ["cut_off_material", "drop_off_material"]) +def test_solid_model_materials(parent_object, tree_object, acp_instance, material_type): + """Check that solid model materials are supported since 25.1.""" + tree_object.cut_off_material_handling = CutoffMaterialType.CUSTOM + tree_object.drop_off_material_handling = DropoffMaterialType.CUSTOM + if parse_version(acp_instance.server_version) < parse_version("25.1"): + with pytest.raises(RuntimeError) as exc: + setattr(tree_object, material_type, parent_object.create_material(name="Material")) + assert f"The property '{material_type}' is only editable since version" in str(exc.value) + else: + material = parent_object.create_material(name="new material") + setattr(tree_object, material_type, material) + sm_material = getattr(tree_object, material_type) + assert sm_material.id == material.id From b4ee3919f99f09501ab81df0fa2433c24641a412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:14:04 +0100 Subject: [PATCH 31/96] Add Imported Plies (#600) * expose the thickness of the AnalysisPly tree object. * Add ImportedAnalaysisPly, ImportedProductionPly, ImportedModelingGroup and ImportedModelingPly --- doc/source/api/enum_types.rst | 4 + doc/source/api/tree_objects.rst | 4 + doc/source/index.rst | 1 + src/ansys/acp/core/__init__.py | 16 ++ src/ansys/acp/core/_tree_objects/__init__.py | 16 ++ .../acp/core/_tree_objects/analysis_ply.py | 3 + src/ansys/acp/core/_tree_objects/enums.py | 61 +++++ .../_tree_objects/imported_analysis_ply.py | 78 ++++++ .../_tree_objects/imported_modeling_group.py | 74 ++++++ .../_tree_objects/imported_modeling_ply.py | 241 ++++++++++++++++++ .../_tree_objects/imported_production_ply.py | 84 ++++++ src/ansys/acp/core/_tree_objects/model.py | 13 + .../acp/core/_tree_objects/modeling_group.py | 4 +- .../acp/core/_tree_objects/modeling_ply.py | 8 +- .../_tree_objects/oriented_selection_set.py | 2 + .../acp/core/_tree_objects/production_ply.py | 5 +- tests/conftest.py | 19 ++ tests/data/minimal_complete_model.acph5 | Bin 138204 -> 134196 bytes tests/data/minimal_model_imported_plies.acph5 | Bin 0 -> 793362 bytes tests/unittests/test_analysis_ply.py | 3 + tests/unittests/test_imported_analysis_ply.py | 132 ++++++++++ .../unittests/test_imported_modeling_group.py | 71 ++++++ tests/unittests/test_imported_modeling_ply.py | 157 ++++++++++++ .../unittests/test_imported_production_ply.py | 138 ++++++++++ tests/unittests/test_modeling_ply.py | 3 +- tests/unittests/test_production_ply.py | 3 + 26 files changed, 1131 insertions(+), 9 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/imported_analysis_ply.py create mode 100644 src/ansys/acp/core/_tree_objects/imported_modeling_group.py create mode 100644 src/ansys/acp/core/_tree_objects/imported_modeling_ply.py create mode 100644 src/ansys/acp/core/_tree_objects/imported_production_ply.py create mode 100644 tests/data/minimal_model_imported_plies.acph5 create mode 100644 tests/unittests/test_imported_analysis_ply.py create mode 100644 tests/unittests/test_imported_modeling_group.py create mode 100644 tests/unittests/test_imported_modeling_ply.py create mode 100644 tests/unittests/test_imported_production_ply.py diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 3762cbec43..b612e2a4bc 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -22,10 +22,14 @@ Enumeration data types FeFormat GeometricalRuleType IgnorableEntity + ImportedPlyDrapingType + ImportedPlyOffsetType + ImportedPlyThicknessType IntersectionType LinkedObjectHandling LookUpTable3DInterpolationAlgorithm LookUpTableColumnValueType + MeshImportType NodalDataType OffsetType PlyCutoffType diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index f4130a3755..dbea613e88 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -17,6 +17,10 @@ ACP objects ElementSet Fabric GeometricalSelectionRule + ImportedModelingGroup + ImportedModelingPly + ImportedProductionPly + ImportedAnalysisPly InterfaceLayer LookUpTable1D LookUpTable1DColumn diff --git a/doc/source/index.rst b/doc/source/index.rst index 3db3aeed67..3903b3d4bc 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -78,5 +78,6 @@ Limitations * Only shell workflows are supported, solid models can not yet be defined using PyACP * FieldDefinitions for variable material properties are not supported +* Visualization and mesh data of imported plies are not supported yet * Section cuts cannot be visualized * Sampling point analysis data is not available diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index fcfb28fcf6..d1a90ef7b7 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -78,6 +78,13 @@ GeometricalSelectionRuleElementalData, GeometricalSelectionRuleNodalData, IgnorableEntity, + ImportedAnalysisPly, + ImportedModelingGroup, + ImportedModelingPly, + ImportedPlyDrapingType, + ImportedPlyOffsetType, + ImportedPlyThicknessType, + ImportedProductionPly, InterfaceLayer, IntersectionType, Lamina, @@ -90,6 +97,7 @@ LookUpTableColumnValueType, Material, MeshData, + MeshImportType, Model, ModelElementalData, ModelingGroup, @@ -199,6 +207,13 @@ "get_dpf_unit_system", "get_model_tree", "IgnorableEntity", + "ImportedAnalysisPly", + "ImportedProductionPly", + "ImportedModelingPly", + "ImportedModelingGroup", + "ImportedPlyDrapingType", + "ImportedPlyOffsetType", + "ImportedPlyThicknessType", "InterfaceLayer", "IntersectionType", "Lamina", @@ -215,6 +230,7 @@ "material_property_sets", "Material", "MeshData", + "MeshImportType", "Model", "ModelElementalData", "ModelingGroup", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index f76b6241b7..67ffc9007a 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -55,9 +55,13 @@ ElementalDataType, ExtrusionType, GeometricalRuleType, + ImportedPlyDrapingType, + ImportedPlyOffsetType, + ImportedPlyThicknessType, IntersectionType, LookUpTable3DInterpolationAlgorithm, LookUpTableColumnValueType, + MeshImportType, NodalDataType, OffsetType, PlyCutoffType, @@ -80,6 +84,10 @@ GeometricalSelectionRuleElementalData, GeometricalSelectionRuleNodalData, ) +from .imported_analysis_ply import ImportedAnalysisPly +from .imported_modeling_group import ImportedModelingGroup +from .imported_modeling_ply import ImportedModelingPly +from .imported_production_ply import ImportedProductionPly from .interface_layer import InterfaceLayer from .linked_selection_rule import LinkedSelectionRule from .lookup_table_1d import LookUpTable1D @@ -164,6 +172,13 @@ "GeometricalSelectionRuleElementalData", "GeometricalSelectionRuleNodalData", "IgnorableEntity", + "ImportedAnalysisPly", + "ImportedProductionPly", + "ImportedModelingPly", + "ImportedModelingGroup", + "ImportedPlyDrapingType", + "ImportedPlyOffsetType", + "ImportedPlyThicknessType", "InterfaceLayer", "InterpolationOptions", "IntersectionType", @@ -177,6 +192,7 @@ "LookUpTableColumnValueType", "Material", "MeshData", + "MeshImportType", "Model", "ModelElementalData", "ModelingGroup", diff --git a/src/ansys/acp/core/_tree_objects/analysis_ply.py b/src/ansys/acp/core/_tree_objects/analysis_ply.py index dd4e64ce5c..ade31b1f09 100644 --- a/src/ansys/acp/core/_tree_objects/analysis_ply.py +++ b/src/ansys/acp/core/_tree_objects/analysis_ply.py @@ -96,6 +96,8 @@ class AnalysisPly(ReadOnlyTreeObject, IdTreeObject): Material of the Analysis ply. angle: float Angle of the Analysis ply in degrees. + thickness: float + Thickness of the Analysis ply active_in_post_mode: bool If False, deactivates the failure analysis for this ply during post-processing. @@ -113,6 +115,7 @@ def _create_stub(self) -> analysis_ply_pb2_grpc.ObjectServiceStub: status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) material = grpc_link_property_read_only("properties.material") angle: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.angle") + thickness: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.thickness") active_in_post_mode: ReadOnlyProperty[bool] = grpc_data_property_read_only( "properties.active_in_post_mode" ) diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index fd9a089140..df78ab1542 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -27,6 +27,7 @@ edge_set_pb2, enum_types_pb2, geometrical_selection_rule_pb2, + imported_modeling_ply_pb2, lookup_table_3d_pb2, lookup_table_column_type_pb2, mesh_query_pb2, @@ -72,6 +73,9 @@ "ThicknessType", "UnitSystemType", "VirtualGeometryDimension", + "ImportedPlyDrapingType", + "ImportedPlyOffsetType", + "ImportedPlyThicknessType", ] (StatusType, status_type_to_pb, status_type_from_pb) = wrap_to_string_enum( @@ -125,6 +129,21 @@ doc="Options for the draping algorithm used.", ) +( + ImportedPlyDrapingType, + imported_ply_draping_type_to_pb, + imported_ply_draping_type_from_pb, +) = wrap_to_string_enum( + "ImportedPlyDrapingType", + ply_material_pb2.DrapingType, + module=__name__, + doc="Options for the draping algorithm used.", + explicit_value_list=( + ply_material_pb2.DrapingType.NO_DRAPING, + ply_material_pb2.DrapingType.TABULAR_VALUES, + ), +) + ( DrapingMaterialType, draping_material_type_to_pb, @@ -350,6 +369,21 @@ doc="Options for how ply thickness is defined.", ) +( + ImportedPlyThicknessType, + imported_ply_thickness_type_to_pb, + imported_ply_thickness_type_from_pb, +) = wrap_to_string_enum( + "ImportedPlyThicknessType", + modeling_ply_pb2.ThicknessType, + module=__name__, + doc="Options for how ply thickness is defined.", + explicit_value_list=( + modeling_ply_pb2.ThicknessType.NOMINAL, + modeling_ply_pb2.ThicknessType.FROM_TABLE, + ), +) + ( ThicknessFieldType, thickness_field_type_to_pb, @@ -373,6 +407,33 @@ ), ) +( + ImportedPlyOffsetType, + imported_ply_offset_type_to_pb, + imported_ply_offset_type_from_pb, +) = wrap_to_string_enum( + "ImportedPlyOffsetType", + enum_types_pb2.OffsetType, + module=__name__, + doc="Options for the definition of the offset.", + explicit_value_list=( + enum_types_pb2.OffsetType.MIDDLE_OFFSET, + enum_types_pb2.OffsetType.BOTTOM_OFFSET, + enum_types_pb2.OffsetType.TOP_OFFSET, + ), +) + +( + MeshImportType, + mesh_import_type_to_pb, + mesh_import_type_from_pb, +) = wrap_to_string_enum( + "MeshImportType", + imported_modeling_ply_pb2.MeshImportType, + module=__name__, + doc="Options for the definition of the source of the imported mesh.", +) + (ExtrusionType, extrusion_type_to_pb, extrusion_type_from_pb) = wrap_to_string_enum( "ExtrusionType", section_cut_pb2.ExtrusionType, diff --git a/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py b/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py new file mode 100644 index 0000000000..5a5b42b03e --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py @@ -0,0 +1,78 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable + +from ansys.api.acp.v0 import imported_analysis_ply_pb2, imported_analysis_ply_pb2_grpc + +from .._utils.property_protocols import ReadOnlyProperty +from ._grpc_helpers.property_helper import ( + grpc_data_property_read_only, + grpc_link_property_read_only, + mark_grpc_properties, +) +from .base import IdTreeObject, ReadOnlyTreeObject +from .enums import status_type_from_pb +from .object_registry import register + +__all__ = [ + "ImportedAnalysisPly", +] + + +@mark_grpc_properties +@register +class ImportedAnalysisPly(ReadOnlyTreeObject, IdTreeObject): + """Instantiate an Imported Analysis Ply. + + Parameters + ---------- + name: str + The name of the Imported Analysis Ply. + material: Material + Material of the Imported Analysis ply. + angle: float + Angle of the Analysis ply in degrees. + thickness: float + Thickness of the ply. + active_in_post_mode: bool + If False, deactivates the failure analysis for this ply during post-processing. + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "imported_analysis_plies" + _OBJECT_INFO_TYPE = imported_analysis_ply_pb2.ObjectInfo + _SUPPORTED_SINCE = "25.1" + + def _create_stub(self) -> imported_analysis_ply_pb2_grpc.ObjectServiceStub: + return imported_analysis_ply_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + material = grpc_link_property_read_only("properties.material") + angle: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.angle") + thickness: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.thickness") + active_in_post_mode: ReadOnlyProperty[bool] = grpc_data_property_read_only( + "properties.active_in_post_mode" + ) diff --git a/src/ansys/acp/core/_tree_objects/imported_modeling_group.py b/src/ansys/acp/core/_tree_objects/imported_modeling_group.py new file mode 100644 index 0000000000..e51e64a8e7 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/imported_modeling_group.py @@ -0,0 +1,74 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable + +from ansys.api.acp.v0 import ( + imported_modeling_group_pb2, + imported_modeling_group_pb2_grpc, + imported_modeling_ply_pb2_grpc, +) + +from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping +from ._grpc_helpers.property_helper import mark_grpc_properties +from .base import CreatableTreeObject, IdTreeObject +from .imported_modeling_ply import ImportedModelingPly +from .object_registry import register + +__all__ = ["ImportedModelingGroup"] + + +@mark_grpc_properties +@register +class ImportedModelingGroup(CreatableTreeObject, IdTreeObject): + """Instantiate an imported modeling group. + + Parameters + ---------- + name : str + Name of the imported modeling group. + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "imported_modeling_groups" + _OBJECT_INFO_TYPE = imported_modeling_group_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = imported_modeling_group_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__(self, *, name: str = "ImportedModelingGroup"): + super().__init__(name=name) + + def _create_stub(self) -> imported_modeling_group_pb2_grpc.ObjectServiceStub: + return imported_modeling_group_pb2_grpc.ObjectServiceStub(self._channel) + + create_imported_modeling_ply = define_create_method( + ImportedModelingPly, + func_name="create_imported_modeling_ply", + parent_class_name="ImportedModelingGroup", + module_name=__module__, + ) + imported_modeling_plies = define_mutable_mapping( + ImportedModelingPly, imported_modeling_ply_pb2_grpc.ObjectServiceStub + ) diff --git a/src/ansys/acp/core/_tree_objects/imported_modeling_ply.py b/src/ansys/acp/core/_tree_objects/imported_modeling_ply.py new file mode 100644 index 0000000000..03a06e93ec --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/imported_modeling_ply.py @@ -0,0 +1,241 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable, Sequence + +from ansys.api.acp.v0 import ( + imported_modeling_ply_pb2, + imported_modeling_ply_pb2_grpc, + imported_production_ply_pb2_grpc, +) + +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.linked_object_list import define_linked_object_list +from ._grpc_helpers.mapping import get_read_only_collection_property +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .enums import ( + ImportedPlyDrapingType, + ImportedPlyOffsetType, + ImportedPlyThicknessType, + MeshImportType, + RosetteSelectionMethod, + ThicknessFieldType, + imported_ply_draping_type_from_pb, + imported_ply_draping_type_to_pb, + imported_ply_offset_type_from_pb, + imported_ply_offset_type_to_pb, + imported_ply_thickness_type_from_pb, + imported_ply_thickness_type_to_pb, + mesh_import_type_from_pb, + mesh_import_type_to_pb, + rosette_selection_method_from_pb, + rosette_selection_method_to_pb, + status_type_from_pb, + thickness_field_type_from_pb, + thickness_field_type_to_pb, +) +from .fabric import Fabric +from .imported_production_ply import ImportedProductionPly +from .lookup_table_1d_column import LookUpTable1DColumn +from .lookup_table_3d_column import LookUpTable3DColumn +from .object_registry import register +from .rosette import Rosette +from .virtual_geometry import VirtualGeometry + +__all__ = [ + "ImportedModelingPly", +] + + +@mark_grpc_properties +@register +class ImportedModelingPly(CreatableTreeObject, IdTreeObject): + """Instantiate an Imported Modeling Ply. + + Parameters + ---------- + name : + The name of the Modeling Ply + active : + Inactive plies are ignored in ACP and the downstream analysis. + offset_type : + Defines the location of the mesh (plane) with respect to the laminate. + One of ``BOTTOM_OFFSET``, ``MIDDLE_OFFSET``, and ``TOP_OFFSET``. + mesh_import_type : + Source of the imported mesh. Either from geometry or from hdf5. + mesh_geometry: + Link to the geometry with represents the ply surface. Only used if ``mesh_import_type`` + is ``FROM_GEOMETRY``. + rosette_selection_method : + Selection Method for Rosettes of the Oriented Selection Set. + rosettes : + Rosettes of the Oriented Selection Set. + reference_direction_field : + A 3D lookup table column that defines the reference directions. + rotation_angle : + Angle in degrees by which the initial reference directions are rotated around the orientations. + ply_material : + The material (only fabric is supported) of the ply. + ply_angle : + Design angle between the reference direction and the ply fiber direction. + draping : + Chooses between different draping formulations. ``NO_DRAPING`` by default. + draping_angle_1_field : + Correction angle between the fiber and draped fiber directions, in degree. + draping_angle_2_field : + Correction angle between the transverse and draped transverse directions, + in degree. Optional, uses the same values as ``draping_angle_1_field`` + (no shear) by default. + thickness_type : + Choose :attr:`ThicknessType.FROM_TABLE` to define a ply with variable thickness. + The default value is :attr:`ThicknessType.NOMINAL`, which means the ply + thickness is constant and determined by the thickness of the ply material. + thickness_field : + Defines the look-up table column used to determine the ply thickness. + Only applies if ``thickness_type`` is :attr:`ThicknessType.FROM_TABLE`. + thickness_field_type : + If ``thickness_type`` is :attr:`ThicknessType.FROM_TABLE`, this parameter + determines how the thickness values are interpreted. They can be either + absolute values (:attr:`ThicknessFieldType.ABSOLUTE_VALUES`) or relative + values (:attr:`ThicknessFieldType.RELATIVE_SCALING_FACTOR`). + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "imported_modeling_plies" + _OBJECT_INFO_TYPE = imported_modeling_ply_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = imported_modeling_ply_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "ModelingPly", + active: bool = True, + offset_type: ImportedPlyOffsetType = ImportedPlyOffsetType.MIDDLE_OFFSET, + mesh_import_type: MeshImportType = MeshImportType.FROM_GEOMETRY, + mesh_geometry: VirtualGeometry | None = None, + rosette_selection_method: RosetteSelectionMethod = "minimum_angle", + rosettes: Sequence[Rosette] = tuple(), + reference_direction_field: LookUpTable3DColumn | LookUpTable1DColumn | None = None, + rotation_angle: float = 0.0, + ply_material: Fabric | None = None, + ply_angle: float = 0.0, + draping: ImportedPlyDrapingType = ImportedPlyDrapingType.NO_DRAPING, + draping_angle_1_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, + draping_angle_2_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, + thickness_type: ImportedPlyThicknessType = ImportedPlyThicknessType.NOMINAL, + thickness_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, + thickness_field_type: ThicknessFieldType = ThicknessFieldType.ABSOLUTE_VALUES, + ): + super().__init__(name=name) + + self.active = active + self.offset_type = ImportedPlyOffsetType(offset_type) + self.mesh_import_type = MeshImportType(mesh_import_type) + self.mesh_geometry = mesh_geometry + self.rosette_selection_method = rosette_selection_method + self.rosettes = rosettes + self.reference_direction_field = reference_direction_field + self.rotation_angle = rotation_angle + + self.ply_material = ply_material + self.ply_angle = ply_angle + + self.draping = ImportedPlyDrapingType(draping) + self.draping_angle_1_field = draping_angle_1_field + self.draping_angle_2_field = draping_angle_2_field + + self.thickness_type = ImportedPlyThicknessType(thickness_type) + self.thickness_field = thickness_field + self.thickness_field_type = thickness_field_type + + def _create_stub(self) -> imported_modeling_ply_pb2_grpc.ObjectServiceStub: + return imported_modeling_ply_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + offset_type = grpc_data_property( + "properties.offset_type", + from_protobuf=imported_ply_offset_type_from_pb, + to_protobuf=imported_ply_offset_type_to_pb, + ) + mesh_import_type = grpc_data_property( + "properties.mesh_import_type", + from_protobuf=mesh_import_type_from_pb, + to_protobuf=mesh_import_type_to_pb, + ) + mesh_geometry = grpc_link_property("properties.mesh_geometry", allowed_types=VirtualGeometry) + rosette_selection_method = grpc_data_property( + "properties.rosette_selection_method", + from_protobuf=rosette_selection_method_from_pb, + to_protobuf=rosette_selection_method_to_pb, + ) + rosettes = define_linked_object_list("properties.rosettes", Rosette) + reference_direction_field = grpc_link_property( + "properties.reference_direction_field", + allowed_types=(LookUpTable1DColumn, LookUpTable3DColumn), + ) + rotation_angle: ReadWriteProperty[float, float] = grpc_data_property( + "properties.rotation_angle" + ) + + ply_material = grpc_link_property("properties.ply_material", allowed_types=(Fabric,)) + ply_angle: ReadWriteProperty[float, float] = grpc_data_property("properties.ply_angle") + + draping = grpc_data_property( + "properties.draping", + from_protobuf=imported_ply_draping_type_from_pb, + to_protobuf=imported_ply_draping_type_to_pb, + ) + draping_angle_1_field = grpc_link_property( + "properties.draping_angle_1_field", allowed_types=(LookUpTable1DColumn, LookUpTable3DColumn) + ) + draping_angle_2_field = grpc_link_property( + "properties.draping_angle_2_field", allowed_types=(LookUpTable1DColumn, LookUpTable3DColumn) + ) + + thickness_type = grpc_data_property( + "properties.thickness_type", + from_protobuf=imported_ply_thickness_type_from_pb, + to_protobuf=imported_ply_thickness_type_to_pb, + ) + thickness_field = grpc_link_property( + "properties.thickness_field", allowed_types=(LookUpTable1DColumn, LookUpTable3DColumn) + ) + thickness_field_type = grpc_data_property( + "properties.thickness_field_type", + from_protobuf=thickness_field_type_from_pb, + to_protobuf=thickness_field_type_to_pb, + ) + + imported_production_plies = get_read_only_collection_property( + ImportedProductionPly, imported_production_ply_pb2_grpc.ObjectServiceStub + ) diff --git a/src/ansys/acp/core/_tree_objects/imported_production_ply.py b/src/ansys/acp/core/_tree_objects/imported_production_ply.py new file mode 100644 index 0000000000..f94a59bb6b --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/imported_production_ply.py @@ -0,0 +1,84 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable + +from ansys.api.acp.v0 import ( + imported_analysis_ply_pb2_grpc, + imported_production_ply_pb2, + imported_production_ply_pb2_grpc, +) + +from .._utils.property_protocols import ReadOnlyProperty +from ._grpc_helpers.mapping import get_read_only_collection_property +from ._grpc_helpers.property_helper import ( + grpc_data_property_read_only, + grpc_link_property_read_only, + mark_grpc_properties, +) +from .base import IdTreeObject, ReadOnlyTreeObject +from .enums import status_type_from_pb +from .imported_analysis_ply import ImportedAnalysisPly +from .object_registry import register + +__all__ = [ + "ImportedProductionPly", +] + + +@mark_grpc_properties +@register +class ImportedProductionPly(ReadOnlyTreeObject, IdTreeObject): + """Instantiate an Imported Production Ply. + + Parameters + ---------- + name: str + The name of the imported production ply. + material: Material + Material of the imported production ply. + angle: float + Angle of the imported production ply in degrees. + thickness: float + Thickness of the imported production ply in degrees. + + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "imported_production_plies" + _OBJECT_INFO_TYPE = imported_production_ply_pb2.ObjectInfo + _SUPPORTED_SINCE = "25.1" + + def _create_stub(self) -> imported_production_ply_pb2_grpc.ObjectServiceStub: + return imported_production_ply_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + material = grpc_link_property_read_only("properties.material") + angle: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.angle") + thickness: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.thickness") + + imported_analysis_plies = get_read_only_collection_property( + ImportedAnalysisPly, imported_analysis_ply_pb2_grpc.ObjectServiceStub + ) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index ceda10cce4..724038c22e 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -42,6 +42,7 @@ enum_types_pb2, fabric_pb2_grpc, geometrical_selection_rule_pb2_grpc, + imported_modeling_group_pb2_grpc, lookup_table_1d_pb2_grpc, lookup_table_3d_pb2_grpc, material_pb2, @@ -113,6 +114,7 @@ ) from .fabric import Fabric from .geometrical_selection_rule import GeometricalSelectionRule +from .imported_modeling_group import ImportedModelingGroup from .lookup_table_1d import LookUpTable1D from .lookup_table_3d import LookUpTable3D from .material import Material @@ -702,6 +704,7 @@ def export_modeling_ply_geometries( oriented_selection_sets = define_mutable_mapping( OrientedSelectionSet, oriented_selection_set_pb2_grpc.ObjectServiceStub ) + create_modeling_group = define_create_method( ModelingGroup, func_name="create_modeling_group", @@ -712,6 +715,16 @@ def export_modeling_ply_geometries( ModelingGroup, modeling_group_pb2_grpc.ObjectServiceStub ) + create_imported_modeling_group = define_create_method( + ImportedModelingGroup, + func_name="create_imported_modeling_group", + parent_class_name="Model", + module_name=__module__, + ) + imported_modeling_groups = define_mutable_mapping( + ImportedModelingGroup, imported_modeling_group_pb2_grpc.ObjectServiceStub + ) + create_sampling_point = define_create_method( SamplingPoint, func_name="create_sampling_point", diff --git a/src/ansys/acp/core/_tree_objects/modeling_group.py b/src/ansys/acp/core/_tree_objects/modeling_group.py index 5cc118a7ca..aa0739f4aa 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_group.py +++ b/src/ansys/acp/core/_tree_objects/modeling_group.py @@ -53,14 +53,14 @@ @dataclasses.dataclass class ModelingGroupElementalData(ElementalData): - """Represents elemental data for an Modeling Group.""" + """Represents elemental data for a Modeling Group.""" normal: VectorData | None = None @dataclasses.dataclass class ModelingGroupNodalData(NodalData): - """Represents nodal data for an Modeling Group.""" + """Represents nodal data for a Modeling Group.""" @mark_grpc_properties diff --git a/src/ansys/acp/core/_tree_objects/modeling_ply.py b/src/ansys/acp/core/_tree_objects/modeling_ply.py index 36e174d451..0dd1001bab 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/modeling_ply.py @@ -419,10 +419,6 @@ def _create_stub(self) -> modeling_ply_pb2_grpc.ObjectServiceStub: module_name=__module__, ) - production_plies = get_read_only_collection_property( - ProductionPly, production_ply_pb2_grpc.ObjectServiceStub - ) - thickness_type = grpc_data_property( "properties.thickness_type", from_protobuf=thickness_type_from_pb, @@ -449,5 +445,9 @@ def _create_stub(self) -> modeling_ply_pb2_grpc.ObjectServiceStub: module_name=__module__, ) + production_plies = get_read_only_collection_property( + ProductionPly, production_ply_pb2_grpc.ObjectServiceStub + ) + elemental_data = elemental_data_property(ModelingPlyElementalData) nodal_data = nodal_data_property(ModelingPlyNodalData) diff --git a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py index 064b165f84..b22fbb35c0 100644 --- a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py +++ b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py @@ -150,6 +150,8 @@ class OrientedSelectionSet(CreatableTreeObject, IdTreeObject): Value between ``0`` and ``1`` which determines the amount of deformation in the transverse direction if the draping material model is set to :attr:`DrapingMaterialType.UD`. + rotation_angle : + Angle in degrees by which the initial reference directions are rotated around the orientations. reference_direction_field : A 3D lookup table column that defines the reference directions. diff --git a/src/ansys/acp/core/_tree_objects/production_ply.py b/src/ansys/acp/core/_tree_objects/production_ply.py index eb4cd52123..6101cb1828 100644 --- a/src/ansys/acp/core/_tree_objects/production_ply.py +++ b/src/ansys/acp/core/_tree_objects/production_ply.py @@ -92,11 +92,13 @@ class ProductionPly(ReadOnlyTreeObject, IdTreeObject): Parameters ---------- name: str - The name of the ProductionPly. + The name of the production ply. material: Material Material of the production ply. angle: float Angle of the production ply in degrees. + thickness: float + Thickness of the production ply. """ @@ -112,6 +114,7 @@ def _create_stub(self) -> production_ply_pb2_grpc.ObjectServiceStub: status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) material = grpc_link_property_read_only("properties.material") angle: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.angle") + thickness: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.thickness") elemental_data = elemental_data_property(ProductionPlyElementalData) nodal_data = nodal_data_property(ProductionPlyNodalData) diff --git a/tests/conftest.py b/tests/conftest.py index c41468fa7b..3823bdfc11 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -251,6 +251,25 @@ def inner(relative_file_path="minimal_complete_model_no_matml_link.acph5", forma return inner +@pytest.fixture +def load_model_imported_plies_from_tempfile(model_data_dir, acp_instance): + @contextmanager + def inner(relative_file_path="minimal_model_imported_plies.acph5", format="acp:h5"): + with tempfile.TemporaryDirectory() as tmp_dir: + source_path = model_data_dir / relative_file_path + + if acp_instance.is_remote: + file_path = acp_instance.upload_file(source_path) + else: + # Copy the file to a temporary directory, so the original file is never + # modified. This can happen for example when a geometry reload happens. + file_path = shutil.copy(source_path, tmp_dir) + + yield acp_instance.import_model(path=file_path, format=format) + + return inner + + @pytest.fixture def load_cad_geometry(model_data_dir, acp_instance): @contextmanager diff --git a/tests/data/minimal_complete_model.acph5 b/tests/data/minimal_complete_model.acph5 index b72ff55697928b89a111c2a067ba3bea86203f4b..464dbd4ac6b93c71476348209594b6eb06c97853 100644 GIT binary patch delta 12301 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 Hw~qNA89X|B diff --git a/tests/data/minimal_model_imported_plies.acph5 b/tests/data/minimal_model_imported_plies.acph5 new file mode 100644 index 0000000000000000000000000000000000000000..339cb88d8301ea259a9997cc8bcad3af58944237 GIT binary patch literal 793362 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�yoIOh2E;w@FzGL<@ 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

Jiy&htAK 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 RpLY|V%fo$bUf=t8e+O^8*ysQN literal 0 HcmV?d00001 diff --git a/tests/unittests/test_analysis_ply.py b/tests/unittests/test_analysis_ply.py index 56cbe636f5..bb939c5a51 100644 --- a/tests/unittests/test_analysis_ply.py +++ b/tests/unittests/test_analysis_ply.py @@ -65,16 +65,19 @@ def properties_3_layers(self, model): "status": "UPTODATE", "material": first_material, "angle": 0.0, + "thickness": 0.0001, }, "P1L2__ModelingPly.1": { "status": "UPTODATE", "material": first_material, "angle": 10.0, + "thickness": 0.0001, }, "P1L3__ModelingPly.1": { "status": "UPTODATE", "material": first_material, "angle": 20.0, + "thickness": 0.0001, }, } diff --git a/tests/unittests/test_imported_analysis_ply.py b/tests/unittests/test_imported_analysis_ply.py new file mode 100644 index 0000000000..5ef6a95cad --- /dev/null +++ b/tests/unittests/test_imported_analysis_ply.py @@ -0,0 +1,132 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ImportedAnalysisPly, Model + +from .common.tree_object_tester import TreeObjectTesterReadOnly + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version( + ImportedAnalysisPly._SUPPORTED_SINCE + ): + pytest.skip("ImportedAnalysisPly is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_imported_plies_from_tempfile): + with load_model_imported_plies_from_tempfile() as model: + yield model + + +def get_hdf5_imported_modeling_group(parent_model: Model): + return parent_model.imported_modeling_groups["by hdf5"] + + +def all_imported_analysis_plies(model: Model): + modeling_group = get_hdf5_imported_modeling_group(model) + imported_analysis_plies = [] + for imp in modeling_group.imported_modeling_plies.values(): + for ipp in imp.imported_production_plies.values(): + for iap in ipp.imported_analysis_plies.values(): + imported_analysis_plies.append(iap) + + return imported_analysis_plies + + +class TestImportedAnalysisPly(TreeObjectTesterReadOnly): + COLLECTION_NAME = "imported_analysis_plies" + + @staticmethod + @pytest.fixture + def parent_object(model: Model): + img = get_hdf5_imported_modeling_group(model) + return img.imported_modeling_plies["ud"].imported_production_plies["ImportedProductionPly"] + + @pytest.fixture + def collection_test_data(self, parent_object): + imported_production_ply = parent_object + object_collection = getattr(imported_production_ply, self.COLLECTION_NAME) + object_collection.values() + object_names = ["P1L1__ud"] + object_ids = ["P1L1__ud"] + + return object_collection, object_names, object_ids + + @pytest.fixture + def properties(self, model): + carbon_ud = model.materials["Epoxy Carbon UD (230 GPa) Prepreg"] + woven = model.materials["Epoxy Carbon Woven (230 GPa) Prepreg"] + return { + "P1L1__ud": { + "status": "UPTODATE", + "material": carbon_ud, + "angle": 45.0, + "thickness": 0.0015, + }, + "P1L1__woven": { + "status": "UPTODATE", + "material": woven, + "angle": 30.0, + "thickness": 0.001, + }, + "P1L1__ud 2": { + "status": "UPTODATE", + "material": carbon_ud, + "angle": 45.0, + "thickness": 0.0015, + }, + } + + def test_properties(self, model: Model, properties): + for ply in all_imported_analysis_plies(model): + ref_values = properties[ply.id] + for prop, value in ref_values.items(): + assert getattr(ply, prop) == value + + def test_after_update(self, model): + # Test that list of analysis plies stays up-to-date + # after update that removes analysis plies. + # Check that requesting properties on a removed analysis + # ply throws the expected error + + initial_imported_analysis_plies = all_imported_analysis_plies(model) + + modeling_group = get_hdf5_imported_modeling_group(model) + for imp in modeling_group.imported_modeling_plies.values(): + imp.active = False + model.update() + assert len(all_imported_analysis_plies(model)) == 0 + + for iap in initial_imported_analysis_plies: + with pytest.raises(LookupError, match="Entity not found") as ex: + _ = iap.status + + for imp in modeling_group.imported_modeling_plies.values(): + imp.active = True + + model.update() + assert len(all_imported_analysis_plies(model)) == 3 diff --git a/tests/unittests/test_imported_modeling_group.py b/tests/unittests/test_imported_modeling_group.py new file mode 100644 index 0000000000..b4cb994309 --- /dev/null +++ b/tests/unittests/test_imported_modeling_group.py @@ -0,0 +1,71 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ImportedModelingGroup + +from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version( + ImportedModelingGroup._SUPPORTED_SINCE + ): + pytest.skip("ImportedModelingGroup is not supported on this version of the server.") + + +@pytest.fixture +def parent_object(load_model_imported_plies_from_tempfile): + with load_model_imported_plies_from_tempfile() as model: + yield model + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_imported_modeling_group() + + +class TestImportedModelingGroup(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "imported_modeling_groups" + + @staticmethod + @pytest.fixture + def default_properties(): + return {} + + CREATE_METHOD_NAME = "create_imported_modeling_group" + + @staticmethod + @pytest.fixture + def object_properties(parent_object): + model = parent_object + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_name"), + ], + read_only=[ + ("id", "some_id"), + ], + ) diff --git a/tests/unittests/test_imported_modeling_ply.py b/tests/unittests/test_imported_modeling_ply.py new file mode 100644 index 0000000000..ca19bdf7e5 --- /dev/null +++ b/tests/unittests/test_imported_modeling_ply.py @@ -0,0 +1,157 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ( + ImportedModelingPly, + ImportedPlyDrapingType, + ImportedPlyOffsetType, + ImportedPlyThicknessType, + MeshImportType, + RosetteSelectionMethod, + ThicknessFieldType, +) + +from .common.linked_object_list_tester import LinkedObjectListTestCase, LinkedObjectListTester +from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version( + ImportedModelingPly._SUPPORTED_SINCE + ): + pytest.skip("ImportedModelingPly is not supported on this version of the server.") + + +@pytest.fixture +def minimal_complete_model(load_model_imported_plies_from_tempfile): + with load_model_imported_plies_from_tempfile() as model: + yield model + + +@pytest.fixture +def parent_object(minimal_complete_model): + return minimal_complete_model.imported_modeling_groups["by hdf5"] + + +@pytest.fixture +def existing_imported_modeling_ply(minimal_complete_model): + return minimal_complete_model.imported_modeling_groups["by hdf5"].imported_modeling_plies["ud"] + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_imported_modeling_ply() + + +class TestImportedModelingPly(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "imported_modeling_plies" + CREATE_METHOD_NAME = "create_imported_modeling_ply" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "active": True, + "offset_type": ImportedPlyOffsetType.MIDDLE_OFFSET, + "mesh_import_type": MeshImportType.FROM_GEOMETRY, + "rosette_selection_method": "minimum_angle", + "rosettes": [], + "reference_direction_field": None, + "rotation_angle": 0.0, + "ply_material": None, + "ply_angle": 0.0, + "draping": ImportedPlyDrapingType.NO_DRAPING, + "draping_angle_1_field": None, + "draping_angle_2_field": None, + "thickness_type": ImportedPlyThicknessType.NOMINAL, + "thickness_field": None, + "thickness_field_type": ThicknessFieldType.ABSOLUTE_VALUES, + } + + @staticmethod + @pytest.fixture(params=[v.value for v in ImportedPlyOffsetType]) + def object_properties(request, minimal_complete_model): + ply_material = minimal_complete_model.create_fabric() + create_lut_method = getattr(minimal_complete_model, "create_lookup_table_1d") + lookup_table = create_lut_method() + column_1 = lookup_table.create_column() + column_2 = lookup_table.create_column() + rosette = minimal_complete_model.create_rosette() + virtual_geometry = minimal_complete_model.create_virtual_geometry() + + return ObjectPropertiesToTest( + read_write=[ + ("active", False), + ("offset_type", request.param), + ("mesh_import_type", MeshImportType.FROM_GEOMETRY), + ("mesh_geometry", virtual_geometry), + ("rosette_selection_method", RosetteSelectionMethod.MINIMUM_DISTANCE), + ("rosettes", [rosette]), + ("reference_direction_field", column_1), + ("rotation_angle", 67.2), + ("ply_material", ply_material), + ("ply_angle", 34.5), + ("draping", ImportedPlyDrapingType.TABULAR_VALUES), + ("draping_angle_1_field", column_1), + ("draping_angle_2_field", column_2), + ("thickness_type", ImportedPlyThicknessType.FROM_TABLE), + ("thickness_field", column_1), + ("thickness_field_type", ThicknessFieldType.RELATIVE_SCALING_FACTOR), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) + + +@pytest.fixture +def linked_object_case(tree_object, minimal_complete_model): + return LinkedObjectListTestCase( + parent_object=tree_object, + linked_attribute_name="rosettes", + existing_linked_object_names=(), + linked_object_constructor=minimal_complete_model.create_rosette, + ) + + +linked_object_case_empty = linked_object_case + + +@pytest.fixture +def linked_object_case_nonempty(tree_object, minimal_complete_model): + tree_object.rosettes = [minimal_complete_model.create_rosette(name="rosette 32")] + return LinkedObjectListTestCase( + parent_object=tree_object, + linked_attribute_name="rosettes", + existing_linked_object_names=("rosette 32",), + linked_object_constructor=minimal_complete_model.create_rosette, + ) + + +class TestLinkedObjectLists(LinkedObjectListTester): + pass diff --git a/tests/unittests/test_imported_production_ply.py b/tests/unittests/test_imported_production_ply.py new file mode 100644 index 0000000000..246aae3628 --- /dev/null +++ b/tests/unittests/test_imported_production_ply.py @@ -0,0 +1,138 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ImportedProductionPly, Model + +from .common.tree_object_tester import TreeObjectTesterReadOnly + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version( + ImportedProductionPly._SUPPORTED_SINCE + ): + pytest.skip("ImportedProductionPly is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_imported_plies_from_tempfile): + with load_model_imported_plies_from_tempfile() as model: + yield model + + +def get_hdf5_imported_modeling_group(parent_model: Model): + return parent_model.imported_modeling_groups["by hdf5"] + + +def all_imported_production_plies(model: Model): + modeling_group = get_hdf5_imported_modeling_group(model) + imported_production_plies = [] + for imp in modeling_group.imported_modeling_plies.values(): + for ipp in imp.imported_production_plies.values(): + imported_production_plies.append(ipp) + + return imported_production_plies + + +class TestImportedProductionPly(TreeObjectTesterReadOnly): + COLLECTION_NAME = "imported_production_plies" + + @staticmethod + @pytest.fixture + def parent_object(model: Model): + img = get_hdf5_imported_modeling_group(model) + return img.imported_modeling_plies["ud"] + + @pytest.fixture + def collection_test_data(self, parent_object): + imported_modeling_ply = parent_object + object_collection = getattr(imported_modeling_ply, self.COLLECTION_NAME) + object_collection.values() + object_names = ["P1__ud"] + object_ids = ["ImportedProductionPly"] + + return object_collection, object_names, object_ids + + @pytest.fixture + def properties(self, model): + fabric_ud = model.fabrics["ud_1.5mm"] + fabric_woven = model.fabrics["woven_1mm"] + return { + "ImportedProductionPly": { + "name": "P1__ud", + "status": "UPTODATE", + "material": fabric_ud, + "angle": 45.0, + "thickness": 0.0015, + }, + "ImportedProductionPly.2": { + "name": "P1__woven", + "status": "UPTODATE", + "material": fabric_woven, + "angle": 30.0, + "thickness": 0.001, + }, + "ImportedProductionPly.3": { + "name": "P1__ud 2", + "status": "UPTODATE", + "material": fabric_ud, + "angle": 45.0, + "thickness": 0.0015, + }, + } + + def test_properties(self, model: Model, properties): + for ply in all_imported_production_plies(model): + ref_values = properties[ply.id] + for prop, value in ref_values.items(): + assert getattr(ply, prop) == value + + def test_after_update(self, model): + # Test that list of analysis plies stays up-to-date + # after update that removes analysis plies. + # Check that requesting properties on a removed analysis + # ply throws the expected error + + initial_imported_production_plies = all_imported_production_plies(model) + + modeling_group = get_hdf5_imported_modeling_group(model) + for imp in modeling_group.imported_modeling_plies.values(): + imp.active = False + model.update() + assert len(all_imported_production_plies(model)) == 0 + + for iap in initial_imported_production_plies: + with pytest.raises(LookupError, match="Entity not found") as ex: + _ = iap.status + + for imp in modeling_group.imported_modeling_plies.values(): + imp.active = True + + model.update() + imported_production_plies = all_imported_production_plies(model) + assert len(imported_production_plies) == 3 + + for ipp in imported_production_plies: + assert len(ipp.imported_analysis_plies) == 1 diff --git a/tests/unittests/test_modeling_ply.py b/tests/unittests/test_modeling_ply.py index c38f3dc9e7..a5cc3b6ffd 100644 --- a/tests/unittests/test_modeling_ply.py +++ b/tests/unittests/test_modeling_ply.py @@ -64,6 +64,7 @@ def tree_object(parent_object): class TestModelingPly(NoLockedMixin, TreeObjectTester): COLLECTION_NAME = "modeling_plies" + CREATE_METHOD_NAME = "create_modeling_ply" @staticmethod @pytest.fixture @@ -91,8 +92,6 @@ def default_properties(): "taper_edges": [], } - CREATE_METHOD_NAME = "create_modeling_ply" - @staticmethod @pytest.fixture(params=["create_fabric", "create_stackup", "create_sublaminate"]) def object_properties(request, parent_model): diff --git a/tests/unittests/test_production_ply.py b/tests/unittests/test_production_ply.py index bf754fea79..ef3b174da9 100644 --- a/tests/unittests/test_production_ply.py +++ b/tests/unittests/test_production_ply.py @@ -49,16 +49,19 @@ def properties_3_layers(self, parent_model: Model): "status": "UPTODATE", "material": first_fabric, "angle": 0.0, + "thickness": 0.0001, }, "ProductionPly.2": { "status": "UPTODATE", "material": first_fabric, "angle": 0.0, + "thickness": 0.0001, }, "ProductionPly.3": { "status": "UPTODATE", "material": first_fabric, "angle": 0.0, + "thickness": 0.0001, }, } From 9a3bcd19064ed9d19d9ba5c7fd597463e17c5a49 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 28 Oct 2024 14:28:10 +0100 Subject: [PATCH 32/96] Disable failing benchmark run (#633) Disable benchmarks with `100ms` delay, since it fails. To avoid breaking the benchmarks in the future, the `latest` server on Python 3.12 now runs the full benchmark suite even before merging to `main`. Follow-up issue to investigate and fix this benchmark: #634 --- .github/workflows/ci_cd.yml | 2 +- tests/benchmarks/conftest.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 7f8e098b9e..4971951dc8 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -188,7 +188,7 @@ jobs: - name: Benchmarks working-directory: tests/benchmarks run: | - poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --build-benchmark-image --benchmark-json benchmark_output.json --benchmark-group-by=fullname ${{ (matrix.python-version == env.MAIN_PYTHON_VERSION && matrix.server-version == 'latest' && github.ref == 'refs/heads/main') && ' ' || '--validate-benchmarks-only' }} + poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --build-benchmark-image --benchmark-json benchmark_output.json --benchmark-group-by=fullname ${{ (matrix.python-version == env.MAIN_PYTHON_VERSION && matrix.server-version == 'latest') && ' ' || '--validate-benchmarks-only' }} env: LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} IMAGE_NAME: ghcr.io/ansys/acp:${{ matrix.server-version }} diff --git a/tests/benchmarks/conftest.py b/tests/benchmarks/conftest.py index a1ec388173..1056be8b65 100644 --- a/tests/benchmarks/conftest.py +++ b/tests/benchmarks/conftest.py @@ -111,7 +111,10 @@ def launcher_configuration(request): ServerNetworkOptions(delay_ms=0, rate_kbit=1e6), ServerNetworkOptions(delay_ms=1, rate_kbit=1e6), ServerNetworkOptions(delay_ms=10, rate_kbit=1e6), - ServerNetworkOptions(delay_ms=100, rate_kbit=1e6), + # Currently disabled since the server fails to start correctly. + # See https://github.com/ansys/pyacp/issues/634 + # + # ServerNetworkOptions(delay_ms=100, rate_kbit=1e6), ServerNetworkOptions(delay_ms=0, rate_kbit=1e4), ServerNetworkOptions(delay_ms=0, rate_kbit=1e3), ServerNetworkOptions(delay_ms=0, rate_kbit=1e2), From 148ea46600e9ef2509180f4651d8db8f0e625acc Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 30 Oct 2024 14:38:25 +0100 Subject: [PATCH 33/96] Add SolidModel object (#635) Implement SolidModel object Add exposure for the SolidModel object. The drop-off and export settings are implemented as a nested 'TreeAttributeObject', which required some changes to 'base.py' so that nested objects fully support the grpc properties helpers. Closes #441 --- doc/source/api/enum_types.rst | 2 + doc/source/api/tree_objects.rst | 2 + src/ansys/acp/core/__init__.py | 16 + src/ansys/acp/core/_tree_objects/__init__.py | 18 +- .../_grpc_helpers/enum_wrapper.py | 1 + src/ansys/acp/core/_tree_objects/base.py | 55 ++ src/ansys/acp/core/_tree_objects/enums.py | 61 +++ src/ansys/acp/core/_tree_objects/model.py | 10 + .../acp/core/_tree_objects/solid_model.py | 497 ++++++++++++++++++ tests/unittests/common/tree_object_tester.py | 24 +- tests/unittests/test_solid_model.py | 374 +++++++++++++ 11 files changed, 1054 insertions(+), 6 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/solid_model.py create mode 100644 tests/unittests/test_solid_model.py diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index b612e2a4bc..17f59a916c 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -15,10 +15,12 @@ Enumeration data types DrapingMaterialType DrapingType DropoffMaterialType + DropOffType EdgeSetType EdgeSetType ElementalDataType ExtrusionType + ExtrusionMethodType FeFormat GeometricalRuleType IgnorableEntity diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index dbea613e88..21b7235964 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -13,8 +13,10 @@ ACP objects CADGeometry CutoffSelectionRule CylindricalSelectionRule + DropOffSettings EdgeSet ElementSet + ExportSettings Fabric GeometricalSelectionRule ImportedModelingGroup diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index d1a90ef7b7..b311d638d2 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -63,12 +63,16 @@ DrapingMaterialType, DrapingType, DropoffMaterialType, + DropOffSettings, + DropOffType, EdgeSet, EdgeSetType, ElementalDataType, ElementSet, ElementSetElementalData, ElementSetNodalData, + ExportSettings, + ExtrusionMethodType, ExtrusionType, Fabric, FabricWithAngle, @@ -106,6 +110,7 @@ ModelingPlyNodalData, ModelNodalData, NodalDataType, + OffsetDirectionType, OffsetType, OrientedSelectionSet, OrientedSelectionSetElementalData, @@ -129,6 +134,9 @@ SectionCutType, Sensor, SensorType, + SolidModel, + SolidModelExportFormat, + SolidModelSkinExportFormat, SphericalSelectionRule, SphericalSelectionRuleElementalData, SphericalSelectionRuleNodalData, @@ -187,6 +195,8 @@ "DrapingMaterialType", "DrapingType", "DropoffMaterialType", + "DropOffSettings", + "DropOffType", "EdgeSet", "EdgeSetType", "ElementalDataType", @@ -194,6 +204,8 @@ "ElementSetElementalData", "ElementSetNodalData", "example_helpers", + "ExportSettings", + "ExtrusionMethodType", "ExtrusionType", "Fabric", "FabricWithAngle", @@ -239,6 +251,7 @@ "ModelingPlyNodalData", "ModelNodalData", "NodalDataType", + "OffsetDirectionType", "OffsetType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", @@ -264,6 +277,9 @@ "SectionCutType", "Sensor", "SensorType", + "SolidModel", + "SolidModelExportFormat", + "SolidModelSkinExportFormat", "SphericalSelectionRule", "SphericalSelectionRuleElementalData", "SphericalSelectionRuleNodalData", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 67ffc9007a..c8d96793ae 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -51,8 +51,10 @@ DrapingMaterialType, DrapingType, DropoffMaterialType, + DropOffType, EdgeSetType, ElementalDataType, + ExtrusionMethodType, ExtrusionType, GeometricalRuleType, ImportedPlyDrapingType, @@ -63,6 +65,7 @@ LookUpTableColumnValueType, MeshImportType, NodalDataType, + OffsetDirectionType, OffsetType, PlyCutoffType, PlyGeometryExportFormat, @@ -71,6 +74,8 @@ RosetteType, SectionCutType, SensorType, + SolidModelExportFormat, + SolidModelSkinExportFormat, StatusType, SymmetryType, ThicknessFieldType, @@ -113,6 +118,7 @@ from .sampling_point import SamplingPoint from .section_cut import SectionCut from .sensor import Sensor +from .solid_model import DropOffSettings, ExportSettings, SolidModel from .spherical_selection_rule import ( SphericalSelectionRule, SphericalSelectionRuleElementalData, @@ -156,12 +162,16 @@ "DrapingMaterialType", "DrapingType", "DropoffMaterialType", + "DropOffSettings", + "DropOffType", "EdgeSet", "EdgeSetType", "ElementalDataType", "ElementSet", "ElementSetElementalData", "ElementSetNodalData", + "ExportSettings", + "ExtrusionMethodType", "ExtrusionType", "Fabric", "FabricWithAngle", @@ -173,12 +183,12 @@ "GeometricalSelectionRuleNodalData", "IgnorableEntity", "ImportedAnalysisPly", - "ImportedProductionPly", - "ImportedModelingPly", "ImportedModelingGroup", + "ImportedModelingPly", "ImportedPlyDrapingType", "ImportedPlyOffsetType", "ImportedPlyThicknessType", + "ImportedProductionPly", "InterfaceLayer", "InterpolationOptions", "IntersectionType", @@ -201,6 +211,7 @@ "ModelingPlyNodalData", "ModelNodalData", "NodalDataType", + "OffsetDirectionType", "OffsetType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", @@ -225,6 +236,9 @@ "SectionCutType", "Sensor", "SensorType", + "SolidModel", + "SolidModelExportFormat", + "SolidModelSkinExportFormat", "SphericalSelectionRule", "SphericalSelectionRuleElementalData", "SphericalSelectionRuleNodalData", diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py index ac36bd5af4..b87cf98946 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py @@ -93,6 +93,7 @@ def wrap_to_string_enum( res_enum.__doc__ = doc def to_pb_conversion_func(val: _StrEnumT) -> int: + val = res_enum(val) # generates a nicer error if 'val' is not a valid enum value return to_pb_conversion_dict[val] def from_pb_conversion_func(val: int) -> _StrEnumT: diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index 045b24e50b..480eb4323b 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -47,6 +47,7 @@ tree_object_from_resource_path, ) from ._grpc_helpers.property_helper import ( + _exposed_grpc_property, _get_data_attribute, grpc_data_property, grpc_data_property_read_only, @@ -433,6 +434,12 @@ def _is_stored(self) -> bool: return False return self._parent_object._is_stored + @property + def _server_wrapper(self) -> ServerWrapper: + if self._parent_object is None: + raise RuntimeError("The parent object is not set.") + return self._parent_object._server_wrapper + class PolymorphicMixin(TreeObjectAttributeReadOnly): """Mixin class for attributes which can have multiple types, through a 'oneof' definition.""" @@ -491,6 +498,54 @@ def _put_if_stored(self) -> None: self._put() +class TreeObjectAttributeWithCache(ObjectCacheMixin, TreeObjectAttribute): + """Tree object attribute with instance caching.""" + + @classmethod + @constructor_with_cache( + key_getter=lambda parent_object, attribute_path: ( + parent_object, + attribute_path, + ), + raise_on_invalid_key=True, + ) + def _from_parent(cls: type[Self], /, parent_object: TreeObject, attribute_path: str) -> Self: + return cls(_parent_object=parent_object, _attribute_path=attribute_path) + + @staticmethod + def _cache_key_valid(key: tuple[Editable, str]) -> bool: + parent_object, attribute_path = key + return parent_object is not None and bool(attribute_path) + + +AttribT = TypeVar("AttribT", bound=TreeObjectAttributeWithCache) + + +def nested_grpc_object_property( + pb_path: str, + object_type: type[AttribT], +) -> ReadWriteProperty[AttribT, AttribT]: + """Create a property for a nested object attribute. + + Creates a property for a nested object which is backed by the parent + object's protobuf object. The property is read-write, and instances + are cached. + """ + + def _getter(self: TreeObject) -> AttribT: + return object_type._from_parent(parent_object=self, attribute_path=pb_path) + + def _setter(self: TreeObject, value: AttribT) -> None: + if not isinstance(value, object_type): + raise TypeError( + f"Expected an object of type '{object_type.__name__}', got '{type(value).__name__}'." + ) + _getter(self)._pb_object.CopyFrom(value._pb_object) + self._put_if_stored() + + return _exposed_grpc_property(_getter).setter(_setter) + + if typing.TYPE_CHECKING: # pragma: no cover # Ensure that the ReadOnlyTreeObject satisfies the Gettable interface _x: Readable = typing.cast(ReadOnlyTreeObject, None) diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index df78ab1542..f97ee15f6d 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -36,6 +36,7 @@ rosette_pb2, section_cut_pb2, sensor_pb2, + solid_model_pb2, unit_system_pb2, virtual_geometry_pb2, ) @@ -51,15 +52,18 @@ "DrapingMaterialType", "DrapingType", "DropoffMaterialType", + "DropOffType", "EdgeSetType", "ElementalDataType", "ExtrusionType", + "ExtrusionMethodType", "GeometricalRuleType", "IntersectionType", "LookUpTable3DInterpolationAlgorithm", "LookUpTableColumnValueType", "NodalDataType", "OffsetType", + "OffsetDirectionType", "PlyCutoffType", "PlyGeometryExportFormat", "PlyType", @@ -69,6 +73,8 @@ "SensorType", "StatusType", "SymmetryType", + "SolidModelExportFormat", + "SolidModelSkinExportFormat", "ThicknessFieldType", "ThicknessType", "UnitSystemType", @@ -454,3 +460,58 @@ module=__name__, doc="Determines how the intersection is computed for wireframe section cuts.", ) + +(ExtrusionMethodType, extrusion_method_type_to_pb, extrusion_method_type_from_pb) = ( + wrap_to_string_enum( + "ExtrusionMethodType", + solid_model_pb2.ExtrusionMethodType, + module=__name__, + doc="Extrusion method used in a solid model.", + ) +) + +(OffsetDirectionType, offset_direction_type_to_pb, offset_direction_type_from_pb) = ( + wrap_to_string_enum( + "OffsetDirectionType", + solid_model_pb2.OffsetDirectionType, + module=__name__, + doc=( + "Determines how the offset direction is evaluated in a solid model. With " + "``SURFACE_NORMAL``, the offset direction is re-evaluated based on the " + "surface of the solid. With ``SHELL_NORMAL``, the direction is based on the " + "shell surface." + ), + ) +) +(DropOffType, drop_off_type_to_pb, drop_off_type_from_pb) = wrap_to_string_enum( + "DropOffType", + solid_model_pb2.DropOffType, + module=__name__, + doc="Determines whether the drop off in solid models is inside or outside the ply boundary.", +) + +SolidModelExportFormat, solid_model_export_format_to_pb, _ = wrap_to_string_enum( + "SolidModelExportFormat", + enum_types_pb2.FileFormat, + module=__name__, + value_converter=lambda val: val.lower().replace("_", ":"), + doc="Options for the export format of solid models.", + explicit_value_list=( + enum_types_pb2.FileFormat.ANSYS_H5, + enum_types_pb2.FileFormat.ANSYS_CDB, + ), +) + +SolidModelSkinExportFormat, solid_model_skin_export_format_to_pb, _ = wrap_to_string_enum( + "SolidModelSkinExportFormat", + enum_types_pb2.FileFormat, + module=__name__, + value_converter=lambda val: val.lower().replace("_", ":"), + doc="Options for the export format of solid model skins.", + explicit_value_list=( + enum_types_pb2.FileFormat.ANSYS_CDB, + enum_types_pb2.FileFormat.STEP, + enum_types_pb2.FileFormat.IGES, + enum_types_pb2.FileFormat.STL, + ), +) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 724038c22e..3c99c2d562 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -59,6 +59,7 @@ sampling_point_pb2_grpc, section_cut_pb2_grpc, sensor_pb2_grpc, + solid_model_pb2_grpc, spherical_selection_rule_pb2_grpc, stackup_pb2_grpc, sublaminate_pb2_grpc, @@ -127,6 +128,7 @@ from .sampling_point import SamplingPoint from .section_cut import SectionCut from .sensor import Sensor +from .solid_model import SolidModel from .spherical_selection_rule import SphericalSelectionRule from .stackup import Stackup from .sublaminate import SubLaminate @@ -743,6 +745,14 @@ def export_modeling_ply_geometries( ) section_cuts = define_mutable_mapping(SectionCut, section_cut_pb2_grpc.ObjectServiceStub) + create_solid_model = define_create_method( + SolidModel, + func_name="create_solid_model", + parent_class_name="Model", + module_name=__module__, + ) + solid_models = define_mutable_mapping(SolidModel, solid_model_pb2_grpc.ObjectServiceStub) + create_sensor = define_create_method( Sensor, func_name="create_sensor", parent_class_name="Model", module_name=__module__ ) diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py new file mode 100644 index 0000000000..f60aad0876 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -0,0 +1,497 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable +import typing +from typing import Any + +from ansys.api.acp.v0 import solid_model_export_pb2, solid_model_pb2, solid_model_pb2_grpc + +from .._typing_helper import PATH as _PATH +from .._utils.path_to_str import path_to_str_checked +from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from ._grpc_helpers.exceptions import wrap_grpc_errors +from ._grpc_helpers.linked_object_list import ( + define_linked_object_list, + define_polymorphic_linked_object_list, +) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from .base import ( + CreatableTreeObject, + IdTreeObject, + TreeObjectAttributeWithCache, + nested_grpc_object_property, +) +from .edge_set import EdgeSet +from .element_set import ElementSet +from .enums import ( + DropOffType, + ExtrusionMethodType, + OffsetDirectionType, + SolidModelExportFormat, + SolidModelSkinExportFormat, + drop_off_type_from_pb, + drop_off_type_to_pb, + extrusion_method_type_from_pb, + extrusion_method_type_to_pb, + offset_direction_type_from_pb, + offset_direction_type_to_pb, + solid_model_export_format_to_pb, + solid_model_skin_export_format_to_pb, + status_type_from_pb, +) +from .material import Material +from .modeling_ply import ModelingPly +from .object_registry import register +from .oriented_selection_set import OrientedSelectionSet + +__all__ = ["SolidModel", "DropOffSettings", "ExportSettings"] + + +@mark_grpc_properties +class DropOffSettings(TreeObjectAttributeWithCache): + """Defines the drop-off settings for a solid model. + + Parameters + ---------- + drop_off_type : + Determines whether the ply's drop-off is inside or outside the boundary + of the ply. + disable_dropoffs_on_bottom : + Whether to remove drop-offs on the bottom surface of the laminate. + dropoff_disabled_on_bottom_sets : + Element sets or oriented selection sets on which drop-offs at the bottom + surface are disabled. + disable_dropoffs_on_top : + Whether to remove drop-offs on the top surface of the laminate. + dropoff_disabled_on_top_sets : + Element sets or oriented selection sets on which drop-offs at the top + surface are disabled. + connect_butt_joined_plies : + Prevent an element drop-off of two adjacent, sequential plies in the same + modeling group. + """ + + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + drop_off_type: DropOffType = DropOffType.INSIDE_PLY, + disable_dropoffs_on_bottom: bool = False, + dropoff_disabled_on_bottom_sets: Iterable[ElementSet | OrientedSelectionSet] = (), + disable_dropoffs_on_top: bool = False, + dropoff_disabled_on_top_sets: Iterable[ElementSet | OrientedSelectionSet] = (), + connect_butt_joined_plies: bool = True, + _parent_object: SolidModel | None = None, + _pb_object: Any | None = None, + _attribute_path: str | None = None, + ): + super().__init__( + _parent_object=_parent_object, + _pb_object=_pb_object, + _attribute_path=_attribute_path, + ) + # The '__init__' method can be called either with the explicit values + # defined below, or from a parent object or protobuf object. In the case + # where a parent object or protobuf object is provided, the explicit values + # must not be set, otherwise the default values will override the existing + # values. + if _parent_object is None and _pb_object is None: + self.drop_off_type = drop_off_type + self.disable_dropoffs_on_bottom = disable_dropoffs_on_bottom + self.dropoff_disabled_on_bottom_sets = dropoff_disabled_on_bottom_sets + self.disable_dropoffs_on_top = disable_dropoffs_on_top + self.dropoff_disabled_on_top_sets = dropoff_disabled_on_top_sets + self.connect_butt_joined_plies = connect_butt_joined_plies + + @classmethod + def _create_default_pb_object(self) -> solid_model_pb2.DropOffSettings: + # There's no need to define the 'real' default values here, since + # this method is only called when the object is created from scratch. + # In that case, the '__init__' method will be called with the default + # values defined there. + # NOTE dgresch Oct'24: I'm not sure if '_create_default_pb_object' is + # the right abstraction overall. + # The concept _could_ be useful for avoiding the duplicate logic of + # checking '_parent_object is None and _pb_object is None' (this class's + # '__init__', and the 'TreeObjectAttribute.__init__'). + # But, it would need to be extended to allow also _overriding_ the defaults + # on calling __init__. + return solid_model_pb2.DropOffSettings() + + drop_off_type = grpc_data_property( + "drop_off_type", + from_protobuf=drop_off_type_from_pb, + to_protobuf=drop_off_type_to_pb, + ) + + disable_dropoffs_on_bottom: ReadWriteProperty[bool, bool] = grpc_data_property( + "disable_dropoffs_on_bottom" + ) + dropoff_disabled_on_bottom_sets = define_polymorphic_linked_object_list( + "dropoff_disabled_on_bottom_sets", allowed_types=(ElementSet, OrientedSelectionSet) + ) + + disable_dropoffs_on_top: ReadWriteProperty[bool, bool] = grpc_data_property( + "disable_dropoffs_on_top" + ) + dropoff_disabled_on_top_sets = define_polymorphic_linked_object_list( + "dropoff_disabled_on_top_sets", allowed_types=(ElementSet, OrientedSelectionSet) + ) + + connect_butt_joined_plies: ReadWriteProperty[bool, bool] = grpc_data_property( + "connect_butt_joined_plies" + ) + + +@mark_grpc_properties +class ExportSettings(TreeObjectAttributeWithCache): + """Defines the settings for exporting a solid model. + + Parameters + ---------- + use_default_section_index : + Use the default start index for sections. + section_index : + Custom start index for sections. + Only used if ``use_default_section_index`` is False. + use_default_coordinate_system_index : + Use the default start index for coordinate systems. + coordinate_system_index : + Custom start index for coordinate systems. + Only used if ``use_default_coordinate_system_index`` is False. + use_default_material_index : + Use the default start index for materials. + material_index : + Custom start index for materials. + Only used if ``use_default_material_index`` is False. + use_default_node_index : + Use the default start index for nodes. + node_index : + Custom start index for nodes. + Only used if ``use_default_node_index`` is False. + use_default_element_index : + Use the default start index for elements. + element_index : + Custom start index for elements. + Only used if ``use_default_element_index`` is False. + use_solsh_elements : + When True, export linear layered elements as Solsh (Solid190). + write_degenerated_elements : + Whether to export drop-off and cut-off elements. + drop_hanging_nodes : + When True, the hanging nodes of quadratic solid meshes are dropped. + use_solid_model_prefix : + Use the solid model name as a prefix for the exported file. + transfer_all_sets : + When True, all element sets and edge sets are exported. + transferred_element_sets : + Element sets to be exported. + Only used if ``transfer_all_sets`` is False. + transferred_edge_sets : + Edge sets to be exported. + Only used if ``transfer_all_sets`` is False. + + """ + + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + use_default_section_index: bool = True, + section_index: int = 0, + use_default_coordinate_system_index: bool = True, + coordinate_system_index: int = 0, + use_default_material_index: bool = True, + material_index: int = 0, + use_default_node_index: bool = True, + node_index: int = 0, + use_default_element_index: bool = True, + element_index: int = 0, + use_solsh_elements: bool = False, + write_degenerated_elements: bool = True, + drop_hanging_nodes: bool = True, + use_solid_model_prefix: bool = True, + transfer_all_sets: bool = True, + transferred_element_sets: Iterable[ElementSet] = (), + transferred_edge_sets: Iterable[EdgeSet] = (), + _parent_object: SolidModel | None = None, + _pb_object: Any | None = None, + _attribute_path: str | None = None, + ): + super().__init__( + _parent_object=_parent_object, + _pb_object=_pb_object, + _attribute_path=_attribute_path, + ) + # See comment on DropOffSettings.__init__ for the logic here. + if _parent_object is None and _pb_object is None: + self.use_default_section_index = use_default_section_index + self.section_index = section_index + self.use_default_coordinate_system_index = use_default_coordinate_system_index + self.coordinate_system_index = coordinate_system_index + self.use_default_material_index = use_default_material_index + self.material_index = material_index + self.use_default_node_index = use_default_node_index + self.node_index = node_index + self.use_default_element_index = use_default_element_index + self.element_index = element_index + self.use_solsh_elements = use_solsh_elements + self.write_degenerated_elements = write_degenerated_elements + self.drop_hanging_nodes = drop_hanging_nodes + self.use_solid_model_prefix = use_solid_model_prefix + self.transfer_all_sets = transfer_all_sets + self.transferred_element_sets = transferred_element_sets + self.transferred_edge_sets = transferred_edge_sets + + @classmethod + def _create_default_pb_object(self) -> solid_model_pb2.ExportSettings: + # See comment on DropOffSettings._create_default_pb_object + return solid_model_pb2.ExportSettings() + + use_default_section_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_section_index" + ) + section_index: ReadWriteProperty[int, int] = grpc_data_property("section_index") + use_default_coordinate_system_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_coordinate_system_index" + ) + coordinate_system_index: ReadWriteProperty[int, int] = grpc_data_property( + "coordinate_system_index" + ) + use_default_material_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_material_index" + ) + material_index: ReadWriteProperty[int, int] = grpc_data_property("material_index") + use_default_node_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_node_index" + ) + node_index: ReadWriteProperty[int, int] = grpc_data_property("node_index") + use_default_element_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_element_index" + ) + element_index: ReadWriteProperty[int, int] = grpc_data_property("element_index") + use_solsh_elements: ReadWriteProperty[bool, bool] = grpc_data_property("use_solsh_elements") + write_degenerated_elements: ReadWriteProperty[bool, bool] = grpc_data_property( + "write_degenerated_elements" + ) + drop_hanging_nodes: ReadWriteProperty[bool, bool] = grpc_data_property("drop_hanging_nodes") + use_solid_model_prefix: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_solid_model_prefix" + ) + transfer_all_sets: ReadWriteProperty[bool, bool] = grpc_data_property("transfer_all_sets") + transferred_element_sets = define_linked_object_list("transferred_element_sets", ElementSet) + transferred_edge_sets = define_linked_object_list("transferred_edge_sets", EdgeSet) + + +@mark_grpc_properties +@register +class SolidModel(CreatableTreeObject, IdTreeObject): + """Instantiate a solid model. + + Parameters + ---------- + name : + Name of the solid model. + active : + Inactive solid models are not computed, and ignored in the analysis. + element_sets : + Element sets or oriented selection sets determining the extent of + the solid model. + extrusion_method : + Determines how plies are bundled in the layered solid elements. + max_element_thickness : + Maximum thickness of the layered solid elements. A new element is + introduced if the thickness exceeds this value. + Only used if the ``extrusion_method`` is one of ``SPECIFY_THICKNESS``, + ``MATERIAL_WISE``, or ``SANDWICH_WISE``. + ply_group_pointers : + Explicitly defines modeling plies where a new element is introduced. + Only used if the ``extrusion_method`` is ``USER_DEFINED``. + offset_direction : + Determines how the extrusion direction is defined. With ``SHELL_NORMAL``, + the normal direction of the shell is used during the entire extrusion. + With ``SURFACE_NORMAL``, the offset direction is re-evaluated based + on the surface of the solid model during the extrusion. + skip_elements_without_plies : + If True, elements without plies are automatically removed from the + region of extrusion. This means that no drop-off elements are created + for these elements. + drop_off_material : + This material is assigned to the layered solid drop-off elements if + ``drop_off_material_handling`` is set to ``GLOBAL`` in the fabric + definition. + cut_off_material : + This material is assigned to the degenerated solid cut-off elements if + ``cut_off_material_handling`` is set to ``GLOBAL`` in the fabric + definition. + delete_bad_elements : + If True, a final element check is performed to remove erroneous elements. + warping_limit : + Maximum allowable warping limit used in the element shape check. Elements + with a warping limit exceeding this value are removed. + Only used if ``delete_bad_elements`` is True. + minimum_volume : + Solid elements with a volume smaller or equal to this value are removed. + With the default value of ``0``, only inverted or zero-volume elements + are removed. + Only used if ``delete_bad_elements`` is True. + drop_off_settings : + Determines how drop-off elements are handled in the solid model extrusion. + export_settings : + Defines the settings for exporting the solid model. + + """ + + __slots__: Iterable[str] = tuple() + _COLLECTION_LABEL = "solid_models" + _OBJECT_INFO_TYPE = solid_model_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = solid_model_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "SolidModel", + active: bool = True, + element_sets: Iterable[ElementSet | OrientedSelectionSet] = (), + extrusion_method: ExtrusionMethodType = ExtrusionMethodType.ANALYSIS_PLY_WISE, + max_element_thickness: float = 1.0, + ply_group_pointers: Iterable[ModelingPly] = (), + offset_direction: OffsetDirectionType = OffsetDirectionType.SHELL_NORMAL, + skip_elements_without_plies: bool = False, + drop_off_material: Material | None = None, + cut_off_material: Material | None = None, + delete_bad_elements: bool = True, + warping_limit: float = 0.4, + minimum_volume: float = 0.0, + drop_off_settings: DropOffSettings = DropOffSettings(), + export_settings: ExportSettings = ExportSettings(), + ): + super().__init__( + name=name, + ) + self.active = active + self.element_sets = element_sets + self.extrusion_method = extrusion_method + self.max_element_thickness = max_element_thickness + self.ply_group_pointers = ply_group_pointers + self.offset_direction = offset_direction + self.skip_elements_without_plies = skip_elements_without_plies + self.drop_off_material = drop_off_material + self.cut_off_material = cut_off_material + self.delete_bad_elements = delete_bad_elements + self.warping_limit = warping_limit + self.minimum_volume = minimum_volume + self.drop_off_settings = drop_off_settings + self.export_settings = export_settings + + def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: + return solid_model_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + locked: ReadOnlyProperty[bool] = grpc_data_property_read_only("properties.locked") + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + + element_sets = define_polymorphic_linked_object_list( + "properties.element_sets", allowed_types=(ElementSet, OrientedSelectionSet) + ) + extrusion_method = grpc_data_property( + "properties.extrusion_method", + from_protobuf=extrusion_method_type_from_pb, + to_protobuf=extrusion_method_type_to_pb, + ) + max_element_thickness: ReadWriteProperty[float, float] = grpc_data_property( + "properties.max_element_thickness" + ) + ply_group_pointers = define_linked_object_list("properties.ply_group_pointers", ModelingPly) + offset_direction = grpc_data_property( + "properties.offset_direction", + from_protobuf=offset_direction_type_from_pb, + to_protobuf=offset_direction_type_to_pb, + ) + skip_elements_without_plies: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.skip_elements_without_plies" + ) + drop_off_material = grpc_link_property("properties.drop_off_material", allowed_types=Material) + cut_off_material = grpc_link_property("properties.cut_off_material", allowed_types=Material) + delete_bad_elements: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.delete_bad_elements" + ) + warping_limit: ReadWriteProperty[float, float] = grpc_data_property("properties.warping_limit") + minimum_volume: ReadWriteProperty[float, float] = grpc_data_property( + "properties.minimum_volume" + ) + + drop_off_settings = nested_grpc_object_property("properties.drop_off_settings", DropOffSettings) + export_settings = nested_grpc_object_property("properties.export_settings", ExportSettings) + + def export(self, *, path: _PATH, format: SolidModelExportFormat) -> None: + """Export the solid model to a file. + + Parameters + ---------- + path : + Path to the file where the solid model is saved. + format : + Format of the exported file. Available formats are ``"ansys:h5"`` + and ``"ansys:cdb"``. + + """ + with wrap_grpc_errors(): + self._get_stub().ExportToFile( # type: ignore + solid_model_export_pb2.ExportToFileRequest( + resource_path=self._resource_path, + path=path_to_str_checked(path), + format=typing.cast(typing.Any, solid_model_export_format_to_pb(format)), + ) + ) + + def export_skin(self, *, path: _PATH, format: SolidModelSkinExportFormat) -> None: + """Export the skin of the solid model to a file. + + Parameters + ---------- + path : + Path to the file where the solid model skin is saved. + format : + Format of the exported file. Available formats are ``"ansys:cdb"``, + ``"step"``, ``"iges"``, and ``"stl"``. + + """ + with wrap_grpc_errors(): + self._get_stub().ExportSkin( # type: ignore + solid_model_export_pb2.ExportSkinRequest( + resource_path=self._resource_path, + path=path_to_str_checked(path), + format=typing.cast(typing.Any, solid_model_skin_export_format_to_pb(format)), + ) + ) diff --git a/tests/unittests/common/tree_object_tester.py b/tests/unittests/common/tree_object_tester.py index f3640cd68c..2150423aa4 100644 --- a/tests/unittests/common/tree_object_tester.py +++ b/tests/unittests/common/tree_object_tester.py @@ -36,6 +36,14 @@ class PropertyWithConversion: converted_value: Any +@dataclass +class PropertyWithCustomComparison: + """A class to store a property which is compared using a custom function.""" + + initial_value: Any + comparison_function: Any + + @dataclass class ObjectPropertiesToTest: read_write: list[tuple[str, Any]] @@ -151,6 +159,9 @@ def create_and_check(init_args: dict[str, Any]): new_object = create_method(**init_args_final) for key, val in init_args.items(): + if isinstance(val, PropertyWithCustomComparison): + assert val.comparison_function(getattr(new_object, key), val.initial_value) + continue if isinstance(val, PropertyWithConversion): val = val.converted_value assert_allclose( @@ -176,13 +187,18 @@ def test_properties(tree_object, object_properties: ObjectPropertiesToTest): if isinstance(value, PropertyWithConversion): initial_value = value.initial_value converted_value = value.converted_value + elif isinstance(value, PropertyWithCustomComparison): + initial_value = converted_value = value.initial_value else: initial_value = converted_value = value setattr(tree_object, prop, initial_value) - assert_allclose( - actual=getattr(tree_object, prop), - desired=converted_value, - ) + if isinstance(value, PropertyWithCustomComparison): + assert value.comparison_function(getattr(tree_object, prop), converted_value) + else: + assert_allclose( + actual=getattr(tree_object, prop), + desired=converted_value, + ) for prop, value in object_properties.read_only: getattr( diff --git a/tests/unittests/test_solid_model.py b/tests/unittests/test_solid_model.py new file mode 100644 index 0000000000..d7111c3bbc --- /dev/null +++ b/tests/unittests/test_solid_model.py @@ -0,0 +1,374 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import pathlib +import tempfile + +from hypothesis import HealthCheck, assume, given, settings +import hypothesis.strategies as st +from packaging.version import parse as parse_version +import pytest + +import ansys.acp.core as pyacp + +from .common.tree_object_tester import ( + ObjectPropertiesToTest, + PropertyWithCustomComparison, + TreeObjectTester, + WithLockedMixin, +) + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version( + pyacp.SolidModel._SUPPORTED_SINCE + ): + pytest.skip("SolidModel is not supported on this version of the server.") + + +def compare_pb_object(given, expected): + if not isinstance(given, type(expected)): + return False + return given._pb_object == expected._pb_object + + +@pytest.fixture +def parent_object(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_solid_model() + + +class TestSolidModel(WithLockedMixin, TreeObjectTester): + COLLECTION_NAME = "solid_models" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + } + + CREATE_METHOD_NAME = "create_solid_model" + INITIAL_OBJECT_NAMES = tuple() + + @staticmethod + @pytest.fixture + def object_properties(parent_object): + model = parent_object + modeling_group = model.create_modeling_group() + + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_name"), + ("active", False), + ( + "element_sets", + [model.create_element_set(), model.create_oriented_selection_set()], + ), + ("extrusion_method", pyacp.ExtrusionMethodType.MONOLITHIC), + ("max_element_thickness", 12.3), + ("ply_group_pointers", [modeling_group.create_modeling_ply() for _ in range(3)]), + ("offset_direction", pyacp.OffsetDirectionType.SURFACE_NORMAL), + ("skip_elements_without_plies", True), + ("drop_off_material", model.create_material()), + ("cut_off_material", model.create_material()), + ("delete_bad_elements", False), + ("warping_limit", 0.6), + ("minimum_volume", 1.2), + ( + "drop_off_settings", + PropertyWithCustomComparison( + initial_value=pyacp.DropOffSettings( + drop_off_type=pyacp.DropOffType.OUTSIDE_PLY, + disable_dropoffs_on_bottom=True, + dropoff_disabled_on_bottom_sets=[ + model.create_element_set(), + model.create_oriented_selection_set(), + ], + disable_dropoffs_on_top=True, + dropoff_disabled_on_top_sets=[ + model.create_oriented_selection_set(), + model.create_element_set(), + ], + connect_butt_joined_plies=False, + ), + comparison_function=compare_pb_object, + ), + ), + ( + "export_settings", + PropertyWithCustomComparison( + initial_value=pyacp.ExportSettings( + use_default_section_index=False, + section_index=2, + use_default_coordinate_system_index=False, + coordinate_system_index=3, + use_default_material_index=False, + material_index=4, + use_default_node_index=False, + node_index=5, + use_default_element_index=False, + element_index=6, + use_solsh_elements=True, + write_degenerated_elements=False, + drop_hanging_nodes=False, + use_solid_model_prefix=False, + transfer_all_sets=False, + transferred_element_sets=[model.create_element_set() for _ in range(2)], + transferred_edge_sets=[model.create_edge_set() for _ in range(3)], + ), + comparison_function=compare_pb_object, + ), + ), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ("locked", True), + ], + ) + + +@given( + disable_dropoffs_on_bottom=st.booleans(), + disable_dropoffs_on_top=st.booleans(), + connect_butt_joined_plies=st.booleans(), +) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +def test_drop_off_settings_on_init( + parent_object, disable_dropoffs_on_bottom, disable_dropoffs_on_top, connect_butt_joined_plies +): + """Check that the drop-off settings are correctly set when passed to the SolidModel constructor.""" + dropoff_disabled_on_bottom_sets = [ + parent_object.create_element_set(), + parent_object.create_oriented_selection_set(), + ] + dropoff_disabled_on_top_sets = [ + parent_object.create_oriented_selection_set(), + parent_object.create_element_set(), + ] + + drop_off_settings = pyacp.DropOffSettings( + disable_dropoffs_on_bottom=disable_dropoffs_on_bottom, + dropoff_disabled_on_bottom_sets=dropoff_disabled_on_bottom_sets, + disable_dropoffs_on_top=disable_dropoffs_on_top, + dropoff_disabled_on_top_sets=dropoff_disabled_on_top_sets, + connect_butt_joined_plies=connect_butt_joined_plies, + ) + + solid_model = parent_object.create_solid_model(drop_off_settings=drop_off_settings) + assert solid_model.drop_off_settings.disable_dropoffs_on_bottom == disable_dropoffs_on_bottom + assert solid_model.drop_off_settings.disable_dropoffs_on_top == disable_dropoffs_on_top + assert solid_model.drop_off_settings.connect_butt_joined_plies == connect_butt_joined_plies + assert ( + solid_model.drop_off_settings.dropoff_disabled_on_bottom_sets + == dropoff_disabled_on_bottom_sets + ) + assert ( + solid_model.drop_off_settings.dropoff_disabled_on_top_sets == dropoff_disabled_on_top_sets + ) + + +@given( + disable_dropoffs_on_bottom=st.booleans(), + disable_dropoffs_on_top=st.booleans(), + connect_butt_joined_plies=st.booleans(), +) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +def test_drop_off_settings_assign( + parent_object, disable_dropoffs_on_bottom, disable_dropoffs_on_top, connect_butt_joined_plies +): + """Check that the drop-off settings are correctly set when assigned to the SolidModel.""" + dropoff_disabled_on_bottom_sets = [ + parent_object.create_element_set(), + parent_object.create_oriented_selection_set(), + ] + dropoff_disabled_on_top_sets = [ + parent_object.create_oriented_selection_set(), + parent_object.create_element_set(), + ] + + drop_off_settings = pyacp.DropOffSettings( + disable_dropoffs_on_bottom=disable_dropoffs_on_bottom, + dropoff_disabled_on_bottom_sets=dropoff_disabled_on_bottom_sets, + disable_dropoffs_on_top=disable_dropoffs_on_top, + dropoff_disabled_on_top_sets=dropoff_disabled_on_top_sets, + connect_butt_joined_plies=connect_butt_joined_plies, + ) + + solid_model = parent_object.create_solid_model() + solid_model.drop_off_settings = drop_off_settings + assert solid_model.drop_off_settings.disable_dropoffs_on_bottom == disable_dropoffs_on_bottom + assert solid_model.drop_off_settings.disable_dropoffs_on_top == disable_dropoffs_on_top + assert solid_model.drop_off_settings.connect_butt_joined_plies == connect_butt_joined_plies + assert ( + solid_model.drop_off_settings.dropoff_disabled_on_bottom_sets + == dropoff_disabled_on_bottom_sets + ) + assert ( + solid_model.drop_off_settings.dropoff_disabled_on_top_sets == dropoff_disabled_on_top_sets + ) + + +def test_drop_off_settings_assign_wrong_type(parent_object): + """Check that assigning the wrong type to the drop-off settings raises an exception.""" + + solid_model = parent_object.create_solid_model() + with pytest.raises(TypeError): + solid_model.drop_off_settings = "wrong_type" + + +def test_drop_off_settings_string_representation(parent_object): + """Check that the string representation of the drop-off settings is correct.""" + solid_model = parent_object.create_solid_model() + dropoff_disabled_on_bottom_sets = [ + parent_object.create_element_set(), + parent_object.create_oriented_selection_set(), + ] + dropoff_disabled_on_top_sets = [ + parent_object.create_oriented_selection_set(), + parent_object.create_element_set(), + ] + + drop_off_settings = pyacp.DropOffSettings( + drop_off_type=pyacp.DropOffType.OUTSIDE_PLY, + disable_dropoffs_on_bottom=True, + dropoff_disabled_on_bottom_sets=dropoff_disabled_on_bottom_sets, + disable_dropoffs_on_top=True, + dropoff_disabled_on_top_sets=dropoff_disabled_on_top_sets, + connect_butt_joined_plies=False, + ) + # When the drop-off settings are not yet linked to the server, the linked + # object lists cannot instantiate the objects. Therefore, the string representation + # will contain '' for the linked objects. + assert "" in str(drop_off_settings) + # Assign the drop-off settings to the solid model, to establish the + # link to the server + solid_model.drop_off_settings = drop_off_settings + str_repr = str(solid_model.drop_off_settings) + assert "DropOffSettings" in str_repr + for val in dropoff_disabled_on_bottom_sets + dropoff_disabled_on_top_sets: + assert repr(val) in str_repr + + +@pytest.mark.parametrize( + "format", + [ + "ansys:h5", + "ansys:cdb", + pyacp.SolidModelExportFormat.ANSYS_H5, + pyacp.SolidModelExportFormat.ANSYS_CDB, + ], +) +def test_solid_model_export(acp_instance, parent_object, format): + """Check that the export to a file works.""" + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempfile.TemporaryDirectory() as tmp_dir: + if format == "ansys:h5": + ext = ".h5" + else: + ext = ".cdb" + + out_file_name = f"out_file{ext}" + out_path = pathlib.Path(tmp_dir) / out_file_name + + if not acp_instance.is_remote: + # save directly to the local file, to avoid a copy in the working directory + out_file_name = out_path # type: ignore + + solid_model.export(path=out_file_name, format=format) + acp_instance.download_file(out_file_name, out_path) + + assert out_path.exists() + assert out_path.stat().st_size > 0 + + +@given(invalid_format=st.text()) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +def test_export_with_invalid_format_raises(parent_object, invalid_format): + """Check that the export to a file with an invalid format raises an exception.""" + assume(invalid_format not in ["ansys:h5", "ansys:cdb"]) + + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempfile.TemporaryDirectory() as tmp_dir: + out_file_name = f"out_file.h5" + out_path = pathlib.Path(tmp_dir) / out_file_name + + with pytest.raises(ValueError): + solid_model.export(path=out_path, format=invalid_format) + + +@pytest.mark.parametrize("format", ["ansys:cdb", "step", "iges", "stl"]) +def test_solid_model_skin_export(acp_instance, parent_object, format): + """Check that the skin export to a file works.""" + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempfile.TemporaryDirectory() as tmp_dir: + ext = {"ansys:cdb": ".cdb", "step": ".stp", "iges": ".igs", "stl": ".stl"}[format] + out_file_name = f"out_file{ext}" + out_path = pathlib.Path(tmp_dir) / out_file_name + + if not acp_instance.is_remote: + # save directly to the local file, to avoid a copy in the working directory + out_file_name = out_path # type: ignore + + solid_model.export_skin(path=out_file_name, format=format) + acp_instance.download_file(out_file_name, out_path) + + assert out_path.exists() + assert out_path.stat().st_size > 0 + + +@given(invalid_format=st.text()) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +def test_skin_export_with_invalid_format_raises(parent_object, invalid_format): + """Check that the export to a file with an invalid format raises an exception.""" + assume(invalid_format not in ["ansys:cdb", "step", "iges", "stl"]) + + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempfile.TemporaryDirectory() as tmp_dir: + out_file_name = f"out_file.stp" + out_path = pathlib.Path(tmp_dir) / out_file_name + + with pytest.raises(ValueError): + solid_model.export_skin(path=out_path, format=invalid_format) From 17fbf433251a3f2ccc1593b8992e70c098d744f9 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 31 Oct 2024 08:58:36 +0100 Subject: [PATCH 34/96] Add snap-to geometry (#636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add exposure for `SnapToGeometry` objects. For the `OrientationType`, the `UNKNOWN` option does not really make sense: it is equivalent to `BOTTOM` in terms of how it's handled in the backend. However, since existing models may use the `UNKNOWN` value, I decided to nevertheless expose it in PyACP, but with a leading underscore, and mentioning in the doc that it should not be used. The alternative would be to convert it to the `BOTTOM` value in the conversion between PyACP enums and protobuf; however, I think this could be confusing when the model definition is different in PyACP vs. the ACP GUI. --------- Co-authored-by: René Roos <105842014+roosre@users.noreply.github.com> --- doc/source/api/enum_types.rst | 1 + doc/source/api/tree_objects.rst | 1 + src/ansys/acp/core/__init__.py | 4 + src/ansys/acp/core/_tree_objects/__init__.py | 4 + src/ansys/acp/core/_tree_objects/enums.py | 24 +++- .../core/_tree_objects/snap_to_geometry.py | 107 ++++++++++++++++++ .../acp/core/_tree_objects/solid_model.py | 19 +++- tests/unittests/test_snap_to_geometry.py | 85 ++++++++++++++ 8 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/snap_to_geometry.py create mode 100644 tests/unittests/test_snap_to_geometry.py diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 17f59a916c..e3186a6cde 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -34,6 +34,7 @@ Enumeration data types MeshImportType NodalDataType OffsetType + OrientationType PlyCutoffType PlyGeometryExportFormat PlyType diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 21b7235964..f5411a59f9 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -39,6 +39,7 @@ ACP objects SamplingPoint SectionCut Sensor + SnapToGeometry SphericalSelectionRule Stackup SubLaminate diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index b311d638d2..ce7bf83441 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -112,6 +112,7 @@ NodalDataType, OffsetDirectionType, OffsetType, + OrientationType, OrientedSelectionSet, OrientedSelectionSetElementalData, OrientedSelectionSetNodalData, @@ -134,6 +135,7 @@ SectionCutType, Sensor, SensorType, + SnapToGeometry, SolidModel, SolidModelExportFormat, SolidModelSkinExportFormat, @@ -253,6 +255,7 @@ "NodalDataType", "OffsetDirectionType", "OffsetType", + "OrientationType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", "OrientedSelectionSetNodalData", @@ -277,6 +280,7 @@ "SectionCutType", "Sensor", "SensorType", + "SnapToGeometry", "SolidModel", "SolidModelExportFormat", "SolidModelSkinExportFormat", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index c8d96793ae..be2b0054ff 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -67,6 +67,7 @@ NodalDataType, OffsetDirectionType, OffsetType, + OrientationType, PlyCutoffType, PlyGeometryExportFormat, PlyType, @@ -118,6 +119,7 @@ from .sampling_point import SamplingPoint from .section_cut import SectionCut from .sensor import Sensor +from .snap_to_geometry import SnapToGeometry from .solid_model import DropOffSettings, ExportSettings, SolidModel from .spherical_selection_rule import ( SphericalSelectionRule, @@ -213,6 +215,7 @@ "NodalDataType", "OffsetDirectionType", "OffsetType", + "OrientationType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", "OrientedSelectionSetNodalData", @@ -236,6 +239,7 @@ "SectionCutType", "Sensor", "SensorType", + "SnapToGeometry", "SolidModel", "SolidModelExportFormat", "SolidModelSkinExportFormat", diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index f97ee15f6d..16f30a50cb 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -36,6 +36,7 @@ rosette_pb2, section_cut_pb2, sensor_pb2, + snap_to_geometry_pb2, solid_model_pb2, unit_system_pb2, virtual_geometry_pb2, @@ -62,8 +63,9 @@ "LookUpTable3DInterpolationAlgorithm", "LookUpTableColumnValueType", "NodalDataType", - "OffsetType", "OffsetDirectionType", + "OffsetType", + "OrientationType", "PlyCutoffType", "PlyGeometryExportFormat", "PlyType", @@ -515,3 +517,23 @@ enum_types_pb2.FileFormat.STL, ), ) + + +def _prefix_undefined(value: str) -> str: + if value == "UNDEFINED": + return "_UNDEFINED" + return value + + +OrientationType, orientation_type_to_pb, orientation_type_from_pb = wrap_to_string_enum( + "OrientationType", + snap_to_geometry_pb2.OrientationType, + module=__name__, + key_converter=_prefix_undefined, + value_converter=lambda val: _prefix_undefined(val).lower(), + doc=( + "Determines which layup face a snap-to geometry is applies to. Note that the " + "``_UNDEFINED`` option should not be used. It is equivalent to using " + "``BOTTOM``, and included only for compatibility with existing models." + ), +) diff --git a/src/ansys/acp/core/_tree_objects/snap_to_geometry.py b/src/ansys/acp/core/_tree_objects/snap_to_geometry.py new file mode 100644 index 0000000000..860410e237 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/snap_to_geometry.py @@ -0,0 +1,107 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable + +from ansys.api.acp.v0 import snap_to_geometry_pb2, snap_to_geometry_pb2_grpc + +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .enums import ( + OrientationType, + orientation_type_from_pb, + orientation_type_to_pb, + status_type_from_pb, +) +from .object_registry import register +from .oriented_selection_set import OrientedSelectionSet +from .virtual_geometry import VirtualGeometry + +__all__ = ["SnapToGeometry"] + + +@mark_grpc_properties +@register +class SnapToGeometry(CreatableTreeObject, IdTreeObject): + """Instantiate a snap-to geometry. + + Parameters + ---------- + name : + Name of the snap-to geometry. + active : + Inactive snap-to geometries are not used in the solid model extrusion. + orientation_type : + Determines whether the snap-to geometry is applied to the top or bottom + surface of the layup. + cad_geometry : + The geometry to snap to. + oriented_selection_set : + Defines the extent over which the snap-to geometry is applied. The normal + of the oriented selection set is used to determine the top and bottom + surfaces of the layup. + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "snap_to_geometries" + _OBJECT_INFO_TYPE = snap_to_geometry_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = snap_to_geometry_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "SnapToGeometry", + active: bool = True, + orientation_type: OrientationType = OrientationType.TOP, + cad_geometry: VirtualGeometry | None = None, + oriented_selection_set: OrientedSelectionSet | None = None, + ): + super().__init__(name=name) + self.active = active + self.orientation_type = orientation_type + self.cad_geometry = cad_geometry + self.oriented_selection_set = oriented_selection_set + + def _create_stub(self) -> snap_to_geometry_pb2_grpc.ObjectServiceStub: + return snap_to_geometry_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + orientation_type = grpc_data_property( + "properties.orientation_type", + from_protobuf=orientation_type_from_pb, + to_protobuf=orientation_type_to_pb, + ) + cad_geometry = grpc_link_property("properties.cad_geometry", allowed_types=(VirtualGeometry,)) + oriented_selection_set = grpc_link_property( + "properties.oriented_selection_set", allowed_types=(OrientedSelectionSet,) + ) diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index f60aad0876..4123352f7e 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -26,7 +26,12 @@ import typing from typing import Any -from ansys.api.acp.v0 import solid_model_export_pb2, solid_model_pb2, solid_model_pb2_grpc +from ansys.api.acp.v0 import ( + snap_to_geometry_pb2_grpc, + solid_model_export_pb2, + solid_model_pb2, + solid_model_pb2_grpc, +) from .._typing_helper import PATH as _PATH from .._utils.path_to_str import path_to_str_checked @@ -36,6 +41,7 @@ define_linked_object_list, define_polymorphic_linked_object_list, ) +from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping from ._grpc_helpers.property_helper import ( grpc_data_property, grpc_data_property_read_only, @@ -70,6 +76,7 @@ from .modeling_ply import ModelingPly from .object_registry import register from .oriented_selection_set import OrientedSelectionSet +from .snap_to_geometry import SnapToGeometry __all__ = ["SolidModel", "DropOffSettings", "ExportSettings"] @@ -454,6 +461,16 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: drop_off_settings = nested_grpc_object_property("properties.drop_off_settings", DropOffSettings) export_settings = nested_grpc_object_property("properties.export_settings", ExportSettings) + create_snap_to_geometry = define_create_method( + SnapToGeometry, + func_name="create_snap_to_geometry", + parent_class_name="SolidModel", + module_name=__module__, + ) + snap_to_geometries = define_mutable_mapping( + SnapToGeometry, snap_to_geometry_pb2_grpc.ObjectServiceStub + ) + def export(self, *, path: _PATH, format: SolidModelExportFormat) -> None: """Export the solid model to a file. diff --git a/tests/unittests/test_snap_to_geometry.py b/tests/unittests/test_snap_to_geometry.py new file mode 100644 index 0000000000..d8235ea7a0 --- /dev/null +++ b/tests/unittests/test_snap_to_geometry.py @@ -0,0 +1,85 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import OrientationType, SnapToGeometry + +from .common.tree_object_tester import ObjectPropertiesToTest, TreeObjectTester, WithLockedMixin + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(SnapToGeometry._SUPPORTED_SINCE): + pytest.skip("SnapToGeometry is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def parent_object(model): + return model.create_solid_model() + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_snap_to_geometry() + + +class TestSnapToGeometry(WithLockedMixin, TreeObjectTester): + COLLECTION_NAME = "snap_to_geometries" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "active": True, + "orientation_type": OrientationType.TOP, + "cad_geometry": None, + "oriented_selection_set": None, + } + + CREATE_METHOD_NAME = "create_snap_to_geometry" + INITIAL_OBJECT_NAMES = tuple() + + @staticmethod + @pytest.fixture + def object_properties(model): + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_snap_to_geometry"), + ("active", False), + ("orientation_type", OrientationType.BOTTOM), + ("cad_geometry", model.create_virtual_geometry()), + ("oriented_selection_set", model.create_oriented_selection_set()), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) From f98ec6bf7d85bd6fe93d0e2dff8a9da18bcafc2e Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 31 Oct 2024 23:31:29 +0100 Subject: [PATCH 35/96] Add type information to create_ and add_ method documentation (#620) Provide type information for the parameters of the ``create_*`` and ``add_*`` methods in the documentation. The ``__signature__`` of the methods is constructed from from the wrapped class's signature, only adding a 'self' parameter. For the ``add_*`` methods, the `Parameters` block (and everything below it) is now also obtained from the class if present. The 'sphinx-autodoc-typehints' extension previously used does not support showing this dynamically created signature, so it was replaced by using the built-in type hint support of 'sphinx.ext.autodoc'. The only drawback is that defaults are now shown only in the signature, and no longer in the description. Since 'sphinx.ext.autodoc' however overwrites the class _parameter_ type hints with the class _attribute_ type hints, the helper function used to generate the signature is monkeypatched. Closes #612 --- doc/make.bat | 1 + doc/source/api/enum_types.rst | 4 +- doc/source/api/tree_objects.rst | 1 + doc/source/conf.py | 92 +++- poetry.lock | 473 +++++++++--------- pyproject.toml | 1 - .../_grpc_helpers/edge_property_list.py | 15 +- .../_tree_objects/_grpc_helpers/mapping.py | 5 + .../_tree_objects/linked_selection_rule.py | 24 +- .../_tree_objects/oriented_selection_set.py | 18 +- src/ansys/acp/core/_tree_objects/sensor.py | 4 +- .../acp/core/_tree_objects/sublaminate.py | 4 +- 12 files changed, 368 insertions(+), 274 deletions(-) diff --git a/doc/make.bat b/doc/make.bat index 294ec1d12c..16ca610eb2 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -10,6 +10,7 @@ if "%SPHINXBUILD%" == "" ( set SOURCEDIR=source set BUILDDIR=build set REPO_ROOT=%~dp0\..\ +set SPHINXOPTS=-n if "%1" == "" goto help if "%1" == "clean" goto clean diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index e3186a6cde..5fd64ec923 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -17,7 +17,6 @@ Enumeration data types DropoffMaterialType DropOffType EdgeSetType - EdgeSetType ElementalDataType ExtrusionType ExtrusionMethodType @@ -33,6 +32,7 @@ Enumeration data types LookUpTableColumnValueType MeshImportType NodalDataType + OffsetDirectionType OffsetType OrientationType PlyCutoffType @@ -42,6 +42,8 @@ Enumeration data types RosetteType SectionCutType SensorType + SolidModelExportFormat + SolidModelSkinExportFormat StatusType SymmetryType ThicknessFieldType diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index f5411a59f9..98ffbf1b8b 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -40,6 +40,7 @@ ACP objects SectionCut Sensor SnapToGeometry + SolidModel SphericalSelectionRule Stackup SubLaminate diff --git a/doc/source/conf.py b/doc/source/conf.py index 656ac6f26c..f3eababf2a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,12 +1,83 @@ """Sphinx documentation configuration file.""" from datetime import datetime +import inspect import os import warnings import pyvista from pyvista.plotting.utilities.sphinx_gallery import DynamicScraper from sphinx.builders.latex import LaTeXBuilder +import sphinx.util.inspect + + +def _signature( + subject, + bound_method: bool = False, + type_aliases=None, +): + """Monkeypatch for 'sphinx.util.inspect.signature'. + + This function defines a custom signature function which is used by the 'sphinx.ext.autodoc'. + The main purpose is to force / fix using the class parameter type hints instead of the class + attribute type hints. + See https://github.com/sphinx-doc/sphinx/issues/11207 for context. + """ + + # Import classes which were guarded with a 'typing.TYPE_CHECKING' explicitly here, otherwise + # the 'eval' in 'inspect.signature' will fail. + from collections.abc import Sequence # noqa: F401 + + from ansys.acp.core import ( # noqa: F401 + BooleanSelectionRule, + CADComponent, + GeometricalSelectionRule, + MeshData, + Model, + ModelingGroup, + ScalarData, + VectorData, + ) + + # Import type aliases so that they can be resolved correctly. + from ansys.acp.core._tree_objects.linked_selection_rule import ( # noqa: F401 + _LINKABLE_SELECTION_RULE_TYPES, + ) + from ansys.acp.core._tree_objects.oriented_selection_set import ( # noqa: F401 + _SELECTION_RULES_LINKABLE_TO_OSS, + ) + from ansys.acp.core._tree_objects.sensor import _LINKABLE_ENTITY_TYPES # noqa: F401 + from ansys.acp.core._tree_objects.sublaminate import _LINKABLE_MATERIAL_TYPES # noqa: F401 + from ansys.dpf.composites.data_sources import ContinuousFiberCompositesFiles # noqa: F401 + from ansys.dpf.core import UnitSystem # noqa: F401 + + signature = inspect.signature(subject, locals=locals(), eval_str=True) + + if signature.parameters: + parameters = list(signature.parameters.values()) + if parameters[0].name == "self": + parameters.pop(0) + # dgresch Oct'24: + # Hack to fix the remaining issues with the signature. This is simpler than + # trying to get 'inspect.signature' to fully work, which would need to be done + # inside the 'define_create_method' function. + # I believe (speculation) the reason for this is that the 'create_' and 'add_' + # methods have an explicit __signature__ attribute, which stops the + # 'inspect.signature' from performing the 'eval'. + for i, param in enumerate(parameters): + if param.annotation in [ + "Sequence[_SELECTION_RULES_LINKABLE_TO_OSS]", + "Sequence[_LINKABLE_ENTITY_TYPES]", + "_LINKABLE_MATERIAL_TYPES", + ]: + parameters[i] = param.replace(annotation=eval(param.annotation)) + signature = signature.replace(parameters=parameters) + return signature + + +sphinx.util.inspect.signature = _signature +napoleon_attr_annotations = False + LaTeXBuilder.supported_image_types = ["image/png", "image/pdf", "image/svg+xml"] from ansys_sphinx_theme import ( @@ -88,7 +159,6 @@ "sphinx.ext.autosummary", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", - "sphinx_autodoc_typehints", "numpydoc", "sphinx_copybutton", ] @@ -117,15 +187,18 @@ } nitpick_ignore = [ - ("py:class", "typing.Self"), - ("py:class", "numpy.float64"), - ("py:class", "numpy.int32"), - ("py:class", "numpy.int64"), # Ignore TypeVar / TypeAlias defined within PyACP: they are either not recognized correctly, # or misidentified as a class. ("py:class", "_PATH"), + ("py:class", "ChildT"), + ("py:class", "CreatableValueT"), ] nitpick_ignore_regex = [ + ("py:class", r"(typing\.|typing_extensions\.)?Self"), + ("py:class", r"(numpy\.typing|npt)\.NDArray"), + ("py:class", r"(numpy|np)\.float64"), + ("py:class", r"(numpy|np)\.int32"), + ("py:class", r"(numpy|np)\.int64"), ("py:class", r"ansys\.api\.acp\..*"), ("py:class", "None -- .*"), # from collections.abc # Ignore TypeVars defined within PyACP: they are either not recognized correctly, @@ -133,15 +206,16 @@ ("py:class", r"^(.*\.)?ValueT$"), ("py:class", r"^(.*\.)?TC$"), ("py:class", r"^(.*\.)?TV$"), + ("py:class", r"ansys\.acp.core\..*\.AttribT"), ("py:class", r"ansys\.acp.core\..*\.ChildT"), ("py:class", r"ansys\.acp.core\..*\.CreatableValueT"), - ("py:class", r"ansys\.acp.core\..*\.MeshDataT"), ("py:class", r"ansys\.acp.core\..*\.ScalarDataT"), + ("py:class", r"ansys\.acp.core\..*\.MeshDataT"), ] -# sphinx_autodoc_typehints configuration -typehints_defaults = "comma" -simplify_optional_unions = True +# sphinx.ext.autodoc configuration +autodoc_typehints = "description" +autodoc_typehints_description_target = "documented_params" # numpydoc configuration numpydoc_show_class_members = False diff --git a/poetry.lock b/poetry.lock index 6b958da2c0..ac9359173d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -392,18 +392,19 @@ tests = ["ansys-mapdl-core (==0.68.5)", "numpy (==2.1.2)", "pyansys-tools-report [[package]] name = "ansys-mechanical-core" -version = "0.11.8" +version = "0.11.9" description = "A python wrapper for Ansys Mechanical" optional = false python-versions = "<4.0,>=3.10" files = [ - {file = "ansys_mechanical_core-0.11.8-py3-none-any.whl", hash = "sha256:fc9cbce588f6ff9d8c877f4c83d59b90cbf36df75d8c994320bffd2d335a3cff"}, - {file = "ansys_mechanical_core-0.11.8.tar.gz", hash = "sha256:607f3d3fb89c13594d81be6692196727f723005aff112f51c4b26a233d3a8277"}, + {file = "ansys_mechanical_core-0.11.9-py3-none-any.whl", hash = "sha256:30f28ff5c5f8abad5a0815eeb2e0e9b330dfad8df238e41d515d72132fb215f7"}, + {file = "ansys_mechanical_core-0.11.9.tar.gz", hash = "sha256:35740cb7e0fcb20ddedf9f84fb5ac7bec22f2bc2fe81f325479737965deb6b66"}, ] [package.dependencies] ansys-api-mechanical = "0.1.2" ansys-mechanical-env = "0.1.8" +ansys-mechanical-stubs = "0.1.4" ansys-platform-instancemanagement = ">=1.0.1" ansys-pythonnet = ">=3.1.0rc2" ansys-tools-path = ">=0.3.1" @@ -412,13 +413,14 @@ click = ">=8.1.3" clr-loader = "0.2.6" grpcio = ">=1.30.0" protobuf = ">=3.12.2,<6" -psutil = "6.0.0" +psutil = "6.1.0" +requests = ">=2,<3" tqdm = ">=4.45.0" [package.extras] -doc = ["ansys-sphinx-theme[autoapi] (==1.1.4)", "grpcio (==1.66.2)", "imageio (==2.36.0)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "panel (==1.5.2)", "plotly (==5.24.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.1.3)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.5.0)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] -tests = ["psutil (==6.0.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.2)"] -viz = ["ansys-tools-visualization-interface (>=0.2.6)", "usd-core (==24.8)"] +doc = ["ansys-sphinx-theme[autoapi] (==1.1.7)", "grpcio (==1.67.0)", "imageio (==2.36.0)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "panel (==1.5.3)", "plotly (==5.24.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.1.3)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.5.0)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] +tests = ["psutil (==6.1.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.2)"] +viz = ["ansys-tools-visualization-interface (>=0.2.6)", "usd-core (==24.11)"] [[package]] name = "ansys-mechanical-env" @@ -436,6 +438,22 @@ ansys-tools-path = ">=0.3.1" click = ">=8.1.3" importlib-metadata = ">=4.0" +[[package]] +name = "ansys-mechanical-stubs" +version = "0.1.4" +description = "PyMechanical scripting API stubs." +optional = false +python-versions = "<4,>=3.10" +files = [ + {file = "ansys_mechanical_stubs-0.1.4-py3-none-any.whl", hash = "sha256:8c62d88f4c4c10c42044781c46e9dd2c68c3ee9813eff33efdc9328ea4fb8648"}, + {file = "ansys_mechanical_stubs-0.1.4.tar.gz", hash = "sha256:c07b5f5421cf59b1ed1806713117180f6ad22e873354637646cdcd3aa06ce853"}, +] + +[package.extras] +build = ["ansys-pythonnet (==3.1.0rc3)"] +doc = ["Sphinx (==7.3.7)", "ansys-sphinx-theme[autoapi] (==1.0.11)", "autodoc_pydantic (==2.0.1)", "jupyter_sphinx (==0.5.3)", "numpydoc (==1.6.0)", "sphinx-autodoc-typehints (==1.25.3)", "sphinx-copybutton (==0.5.2)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-jinja (==2.0.2)", "sphinx-markdown-builder (==0.6.6)", "sphinx-notfound-page (==1.0.2)", "sphinx_design (==0.6.1)", "sphinxcontrib-globalsubs (==0.1.1)", "sphinxcontrib-httpdomain (==1.8.1)", "sphinxnotes-strike (==1.2.1)"] +tests = ["pytest (==8.3.3)", "pytest-cov (==5.0.0)"] + [[package]] name = "ansys-platform-instancemanagement" version = "1.1.2" @@ -457,17 +475,17 @@ tests = ["ansys-api-platform-instancemanagement (==1.0.0)", "grpcio-health-check [[package]] name = "ansys-pythonnet" -version = "3.1.0rc3" +version = "3.1.0rc4" description = ".NET and Mono integration for Python (Ansys, Inc. fork)" optional = false python-versions = "<3.13,>=3.7" files = [ - {file = "ansys-pythonnet-3.1.0rc3.tar.gz", hash = "sha256:369a0a5a838a0991f755b6d63c319ab6997f9dc464d016187227be5cd860a9cb"}, - {file = "ansys_pythonnet-3.1.0rc3-py3-none-any.whl", hash = "sha256:5cc60b510384dd53b6d6aeb2612d1687a83059e3ea460d910b125be246b65c0f"}, + {file = "ansys_pythonnet-3.1.0rc4-py3-none-any.whl", hash = "sha256:ff25706acdaec6dec7b2e6b37488fa366945fa992086131916ba826145984a87"}, + {file = "ansys_pythonnet-3.1.0rc4.tar.gz", hash = "sha256:6b696529197aaaf0a0f0e31e0fea2a9c0fe53e33e6f73dfdedb940196f03abf5"}, ] [package.dependencies] -clr-loader = ">=0.2.6,<0.3.0" +clr-loader = ">=0.2.5,<0.3.0" [[package]] name = "ansys-sphinx-theme" @@ -851,21 +869,20 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "bleach" -version = "6.1.0" +version = "6.2.0" description = "An easy safelist-based HTML-sanitizing tool." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"}, - {file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"}, + {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, + {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, ] [package.dependencies] -six = ">=1.9.0" webencodings = "*" [package.extras] -css = ["tinycss2 (>=1.1.0,<1.3)"] +css = ["tinycss2 (>=1.1.0,<1.5)"] [[package]] name = "cachetools" @@ -1694,13 +1711,13 @@ files = [ [[package]] name = "google-api-core" -version = "2.21.0" +version = "2.22.0" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_core-2.21.0-py3-none-any.whl", hash = "sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d"}, - {file = "google_api_core-2.21.0.tar.gz", hash = "sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81"}, + {file = "google_api_core-2.22.0-py3-none-any.whl", hash = "sha256:a6652b6bd51303902494998626653671703c420f6f4c88cfd3f50ed723e9d021"}, + {file = "google_api_core-2.22.0.tar.gz", hash = "sha256:26f8d76b96477db42b55fd02a33aae4a42ec8b86b98b94969b7333a2c828bf35"}, ] [package.dependencies] @@ -1791,70 +1808,70 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "grpcio" -version = "1.67.0" +version = "1.67.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.67.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc"}, - {file = "grpcio-1.67.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b"}, - {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d"}, - {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9"}, - {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8"}, - {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8"}, - {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4"}, - {file = "grpcio-1.67.0-cp310-cp310-win32.whl", hash = "sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65"}, - {file = "grpcio-1.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6"}, - {file = "grpcio-1.67.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4"}, - {file = "grpcio-1.67.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13"}, - {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee"}, - {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d"}, - {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8"}, - {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf"}, - {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23"}, - {file = "grpcio-1.67.0-cp311-cp311-win32.whl", hash = "sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8"}, - {file = "grpcio-1.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772"}, - {file = "grpcio-1.67.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d"}, - {file = "grpcio-1.67.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273"}, - {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d"}, - {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591"}, - {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52"}, - {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81"}, - {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3"}, - {file = "grpcio-1.67.0-cp312-cp312-win32.whl", hash = "sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955"}, - {file = "grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15"}, - {file = "grpcio-1.67.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a"}, - {file = "grpcio-1.67.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa"}, - {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d"}, - {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af"}, - {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c"}, - {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153"}, - {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03"}, - {file = "grpcio-1.67.0-cp313-cp313-win32.whl", hash = "sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69"}, - {file = "grpcio-1.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210"}, - {file = "grpcio-1.67.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:7f200aca719c1c5dc72ab68be3479b9dafccdf03df530d137632c534bb6f1ee3"}, - {file = "grpcio-1.67.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0892dd200ece4822d72dd0952f7112c542a487fc48fe77568deaaa399c1e717d"}, - {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f4d613fbf868b2e2444f490d18af472ccb47660ea3df52f068c9c8801e1f3e85"}, - {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c69bf11894cad9da00047f46584d5758d6ebc9b5950c0dc96fec7e0bce5cde9"}, - {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9bca3ca0c5e74dea44bf57d27e15a3a3996ce7e5780d61b7c72386356d231db"}, - {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:014dfc020e28a0d9be7e93a91f85ff9f4a87158b7df9952fe23cc42d29d31e1e"}, - {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4ea4509d42c6797539e9ec7496c15473177ce9abc89bc5c71e7abe50fc25737"}, - {file = "grpcio-1.67.0-cp38-cp38-win32.whl", hash = "sha256:9d75641a2fca9ae1ae86454fd25d4c298ea8cc195dbc962852234d54a07060ad"}, - {file = "grpcio-1.67.0-cp38-cp38-win_amd64.whl", hash = "sha256:cff8e54d6a463883cda2fab94d2062aad2f5edd7f06ae3ed030f2a74756db365"}, - {file = "grpcio-1.67.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:62492bd534979e6d7127b8a6b29093161a742dee3875873e01964049d5250a74"}, - {file = "grpcio-1.67.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eef1dce9d1a46119fd09f9a992cf6ab9d9178b696382439446ca5f399d7b96fe"}, - {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f623c57a5321461c84498a99dddf9d13dac0e40ee056d884d6ec4ebcab647a78"}, - {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54d16383044e681f8beb50f905249e4e7261dd169d4aaf6e52eab67b01cbbbe2"}, - {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a44e572fb762c668e4812156b81835f7aba8a721b027e2d4bb29fb50ff4d33"}, - {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:391df8b0faac84d42f5b8dfc65f5152c48ed914e13c522fd05f2aca211f8bfad"}, - {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfd9306511fdfc623a1ba1dc3bc07fbd24e6cfbe3c28b4d1e05177baa2f99617"}, - {file = "grpcio-1.67.0-cp39-cp39-win32.whl", hash = "sha256:30d47dbacfd20cbd0c8be9bfa52fdb833b395d4ec32fe5cff7220afc05d08571"}, - {file = "grpcio-1.67.0-cp39-cp39-win_amd64.whl", hash = "sha256:f55f077685f61f0fbd06ea355142b71e47e4a26d2d678b3ba27248abfe67163a"}, - {file = "grpcio-1.67.0.tar.gz", hash = "sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c"}, + {file = "grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f"}, + {file = "grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d"}, + {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f"}, + {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0"}, + {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa"}, + {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292"}, + {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311"}, + {file = "grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed"}, + {file = "grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e"}, + {file = "grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb"}, + {file = "grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e"}, + {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f"}, + {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc"}, + {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96"}, + {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f"}, + {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970"}, + {file = "grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744"}, + {file = "grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5"}, + {file = "grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953"}, + {file = "grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb"}, + {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0"}, + {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af"}, + {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e"}, + {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75"}, + {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38"}, + {file = "grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78"}, + {file = "grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc"}, + {file = "grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b"}, + {file = "grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1"}, + {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af"}, + {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955"}, + {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8"}, + {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62"}, + {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb"}, + {file = "grpcio-1.67.1-cp313-cp313-win32.whl", hash = "sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121"}, + {file = "grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba"}, + {file = "grpcio-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65"}, + {file = "grpcio-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426"}, + {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab"}, + {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085"}, + {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3"}, + {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8"}, + {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce"}, + {file = "grpcio-1.67.1-cp38-cp38-win32.whl", hash = "sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46"}, + {file = "grpcio-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771"}, + {file = "grpcio-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335"}, + {file = "grpcio-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e"}, + {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8"}, + {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d"}, + {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04"}, + {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8"}, + {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f"}, + {file = "grpcio-1.67.1-cp39-cp39-win32.whl", hash = "sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e"}, + {file = "grpcio-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98"}, + {file = "grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.67.0)"] +protobuf = ["grpcio-tools (>=1.67.1)"] [[package]] name = "grpcio-health-checking" @@ -1887,13 +1904,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.115.5" +version = "6.115.6" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.115.5-py3-none-any.whl", hash = "sha256:b7733459ae9a93020fac3b91b41473c9b85e975139a152a70d88f3a5caa3fa3f"}, - {file = "hypothesis-6.115.5.tar.gz", hash = "sha256:4768c5fb426b305462ed31032d6e216a31daaefb1dc3134fdf2795b7961d7cb3"}, + {file = "hypothesis-6.115.6-py3-none-any.whl", hash = "sha256:d7b7173934753b9624680b38a85749de4fce367c44acb36c08b62765cc0a7a19"}, + {file = "hypothesis-6.115.6.tar.gz", hash = "sha256:d4db48eef183591085676783967e943bb89fef4d596f78c3e4116c61fcc63a6b"}, ] [package.dependencies] @@ -2026,13 +2043,13 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio [[package]] name = "ipython" -version = "8.28.0" +version = "8.29.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, - {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, + {file = "ipython-8.29.0-py3-none-any.whl", hash = "sha256:0188a1bd83267192123ccea7f4a8ed0a78910535dbaa3f37671dca76ebd429c8"}, + {file = "ipython-8.29.0.tar.gz", hash = "sha256:40b60e15b22591450eef73e40a027cf77bd652e757523eebc5bd7c7c498290eb"}, ] [package.dependencies] @@ -3472,13 +3489,13 @@ files = [ [[package]] name = "proto-plus" -version = "1.24.0" +version = "1.25.0" description = "Beautiful, Pythonic protocol buffers." optional = false python-versions = ">=3.7" files = [ - {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, - {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, + {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, + {file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"}, ] [package.dependencies] @@ -3509,32 +3526,33 @@ files = [ [[package]] name = "psutil" -version = "6.0.0" +version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, - {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, - {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, - {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, - {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, - {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, - {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, - {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, - {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, - {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, + {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, + {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, + {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, + {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "ptyprocess" @@ -3734,23 +3752,23 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-benchmark" -version = "4.0.0" +version = "5.1.0" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"}, - {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"}, + {file = "pytest-benchmark-5.1.0.tar.gz", hash = "sha256:9ea661cdc292e8231f7cd4c10b0319e56a2118e2c09d9f50e1b3d150d2aca105"}, + {file = "pytest_benchmark-5.1.0-py3-none-any.whl", hash = "sha256:922de2dfa3033c227c96da942d1878191afa135a29485fb942e85dff1c592c89"}, ] [package.dependencies] py-cpuinfo = "*" -pytest = ">=3.8" +pytest = ">=8.1" [package.extras] aspect = ["aspectlib"] elasticsearch = ["elasticsearch"] -histogram = ["pygal", "pygaljs"] +histogram = ["pygal", "pygaljs", "setuptools"] [[package]] name = "pytest-cases" @@ -3770,17 +3788,17 @@ packaging = "*" [[package]] name = "pytest-cov" -version = "5.0.0" +version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] @@ -4339,23 +4357,23 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "75.2.0" +version = "75.3.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, - {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, + {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, + {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12.*)", "pytest-mypy"] [[package]] name = "simpervisor" @@ -4461,25 +4479,6 @@ docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] -[[package]] -name = "sphinx-autodoc-typehints" -version = "2.5.0" -description = "Type hints (PEP 484) support for the Sphinx autodoc extension" -optional = false -python-versions = ">=3.10" -files = [ - {file = "sphinx_autodoc_typehints-2.5.0-py3-none-any.whl", hash = "sha256:53def4753239683835b19bfa8b68c021388bd48a096efcb02cdab508ece27363"}, - {file = "sphinx_autodoc_typehints-2.5.0.tar.gz", hash = "sha256:259e1026b218d563d72743f417fcc25906a9614897fe37f91bd8d7d58f748c3b"}, -] - -[package.dependencies] -sphinx = ">=8.0.2" - -[package.extras] -docs = ["furo (>=2024.8.6)"] -numpy = ["nptyping (>=2.5)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "defusedxml (>=0.7.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "sphobjinv (>=2.3.1.1)", "typing-extensions (>=4.12.2)"] - [[package]] name = "sphinx-copybutton" version = "0.5.2" @@ -4713,13 +4712,13 @@ typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] [[package]] name = "tinycss2" -version = "1.3.0" +version = "1.4.0" description = "A tiny CSS parser" optional = false python-versions = ">=3.8" files = [ - {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, - {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, + {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, + {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, ] [package.dependencies] @@ -4762,13 +4761,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.5" +version = "4.66.6" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, + {file = "tqdm-4.66.6-py3-none-any.whl", hash = "sha256:223e8b5359c2efc4b30555531f09e9f2f3589bcd7fdd389271191031b49b7a63"}, + {file = "tqdm-4.66.6.tar.gz", hash = "sha256:4bdd694238bef1485ce839d67967ab50af8f9272aab687c0d7702a01da0be090"}, ] [package.dependencies] @@ -4870,13 +4869,13 @@ trame-client = "*" [[package]] name = "types-protobuf" -version = "5.28.0.20240924" +version = "5.28.3.20241030" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types-protobuf-5.28.0.20240924.tar.gz", hash = "sha256:d181af8a256e5a91ce8d5adb53496e880efd9144c7d54483e3653332b60296f0"}, - {file = "types_protobuf-5.28.0.20240924-py3-none-any.whl", hash = "sha256:5cecf612ccdefb7dc95f7a51fb502902f20fc2e6681cd500184aaa1b3931d6a7"}, + {file = "types-protobuf-5.28.3.20241030.tar.gz", hash = "sha256:f7e6b45845d75393fb41c0b3ce82c46d775f9771fae2097414a1dbfe5b51a988"}, + {file = "types_protobuf-5.28.3.20241030-py3-none-any.whl", hash = "sha256:f3dae16adf342d4fb5bb3673cabb22549a6252e5dd66fc52d8310b1a39c64ba9"}, ] [[package]] @@ -4945,13 +4944,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.27.0" +version = "20.27.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, - {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, + {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, + {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, ] [package.dependencies] @@ -5183,93 +5182,93 @@ ssl = ["cryptography"] [[package]] name = "yarl" -version = "1.16.0" +version = "1.17.1" description = "Yet another URL library" optional = false python-versions = ">=3.9" files = [ - {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058"}, - {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2"}, - {file = "yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b"}, - {file = "yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929"}, - {file = "yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7"}, - {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3"}, - {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2"}, - {file = "yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56"}, - {file = "yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c"}, - {file = "yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d"}, - {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104"}, - {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6"}, - {file = "yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3"}, - {file = "yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67"}, - {file = "yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240"}, - {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ace0177520bd4caa99295a9b6fb831d0e9a57d8e0501a22ffaa61b4c024283"}, - {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7118bdb5e3ed81acaa2095cba7ec02a0fe74b52a16ab9f9ac8e28e53ee299732"}, - {file = "yarl-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38fec8a2a94c58bd47c9a50a45d321ab2285ad133adefbbadf3012c054b7e656"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8791d66d81ee45866a7bb15a517b01a2bcf583a18ebf5d72a84e6064c417e64b"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cf936ba67bc6c734f3aa1c01391da74ab7fc046a9f8bbfa230b8393b90cf472"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1aab176dd55b59f77a63b27cffaca67d29987d91a5b615cbead41331e6b7428"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995d0759004c08abd5d1b81300a91d18c8577c6389300bed1c7c11675105a44d"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bc22e00edeb068f71967ab99081e9406cd56dbed864fc3a8259442999d71552"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35b4f7842154176523e0a63c9b871168c69b98065d05a4f637fce342a6a2693a"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7ace71c4b7a0c41f317ae24be62bb61e9d80838d38acb20e70697c625e71f120"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8f639e3f5795a6568aa4f7d2ac6057c757dcd187593679f035adbf12b892bb00"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e8be3aff14f0120ad049121322b107f8a759be76a6a62138322d4c8a337a9e2c"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:122d8e7986043d0549e9eb23c7fd23be078be4b70c9eb42a20052b3d3149c6f2"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fd9c227990f609c165f56b46107d0bc34553fe0387818c42c02f77974402c36"}, - {file = "yarl-1.16.0-cp313-cp313-win32.whl", hash = "sha256:595ca5e943baed31d56b33b34736461a371c6ea0038d3baec399949dd628560b"}, - {file = "yarl-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:921b81b8d78f0e60242fb3db615ea3f368827a76af095d5a69f1c3366db3f596"}, - {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab2b2ac232110a1fdb0d3ffcd087783edd3d4a6ced432a1bf75caf7b7be70916"}, - {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f8713717a09acbfee7c47bfc5777e685539fefdd34fa72faf504c8be2f3df4e"}, - {file = "yarl-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdcffe1dbcb4477d2b4202f63cd972d5baa155ff5a3d9e35801c46a415b7f71a"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a91217208306d82357c67daeef5162a41a28c8352dab7e16daa82e3718852a7"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ab3ed42c78275477ea8e917491365e9a9b69bb615cb46169020bd0aa5e2d6d3"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:707ae579ccb3262dfaef093e202b4c3fb23c3810e8df544b1111bd2401fd7b09"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7a852d1cd0b8d8b37fc9d7f8581152add917a98cfe2ea6e241878795f917ae"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3f1cc3d3d4dc574bebc9b387f6875e228ace5748a7c24f49d8f01ac1bc6c31b"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5ff96da263740779b0893d02b718293cc03400c3a208fc8d8cd79d9b0993e532"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3d375a19ba2bfe320b6d873f3fb165313b002cef8b7cc0a368ad8b8a57453837"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:62c7da0ad93a07da048b500514ca47b759459ec41924143e2ddb5d7e20fd3db5"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:147b0fcd0ee33b4b5f6edfea80452d80e419e51b9a3f7a96ce98eaee145c1581"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:504e1fe1cc4f170195320eb033d2b0ccf5c6114ce5bf2f617535c01699479bca"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bdcf667a5dec12a48f669e485d70c54189f0639c2157b538a4cffd24a853624f"}, - {file = "yarl-1.16.0-cp39-cp39-win32.whl", hash = "sha256:e9951afe6557c75a71045148890052cb942689ee4c9ec29f5436240e1fcc73b7"}, - {file = "yarl-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d7aaa8ff95d0840e289423e7dc35696c2b058d635f945bf05b5cd633146b027"}, - {file = "yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3"}, - {file = "yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4"}, + {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91"}, + {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da"}, + {file = "yarl-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931"}, + {file = "yarl-1.17.1-cp310-cp310-win32.whl", hash = "sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b"}, + {file = "yarl-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243"}, + {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217"}, + {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988"}, + {file = "yarl-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4"}, + {file = "yarl-1.17.1-cp311-cp311-win32.whl", hash = "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7"}, + {file = "yarl-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3"}, + {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61"}, + {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d"}, + {file = "yarl-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199"}, + {file = "yarl-1.17.1-cp312-cp312-win32.whl", hash = "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96"}, + {file = "yarl-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df"}, + {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488"}, + {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374"}, + {file = "yarl-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258"}, + {file = "yarl-1.17.1-cp313-cp313-win32.whl", hash = "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2"}, + {file = "yarl-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda"}, + {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6"}, + {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159"}, + {file = "yarl-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f"}, + {file = "yarl-1.17.1-cp39-cp39-win32.whl", hash = "sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473"}, + {file = "yarl-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138"}, + {file = "yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06"}, + {file = "yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47"}, ] [package.dependencies] @@ -5302,4 +5301,4 @@ examples = ["ansys-dpf-composites", "ansys-mapdl-core", "ansys-mechanical-core", [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "9e737ebe952748709d3c3b8c177d1aebe61367f4dfee36573d6a17c3289e39f6" +content-hash = "ad20e91517c633c119dd7df17612c7ccca7e228f016a54999c9a76a0a62c4761" diff --git a/pyproject.toml b/pyproject.toml index 9580aabcc0..f1072ba834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,6 @@ types-protobuf = ">=4.22.0.2" Sphinx = ">=7.2.6" sphinx-copybutton = ">=0.5.2" -sphinx-autodoc-typehints = ">=2.0.0" numpydoc = ">=1.6.0" ansys-sphinx-theme = ">=1.0.0" pypandoc = ">=1.13" diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py index 6e523ea66a..0b284b1347 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py @@ -23,6 +23,7 @@ from __future__ import annotations from collections.abc import Callable, Iterable, Iterator, MutableSequence +import inspect import sys import textwrap from typing import Any, Concatenate, Protocol, TypeVar, cast, overload @@ -446,9 +447,21 @@ def inner(self: ParentT, /, *args: P.args, **kwargs: P.kwargs) -> ValueT: f"""\ Add a {value_type.__name__} to the {parent_class_name}. - See :class:`.{value_type.__name__}` for the available parameters. """ ) + found_parameters = False + if value_type.__doc__ is not None: + doc_lines = textwrap.dedent(value_type.__doc__).splitlines() + if "Parameters" in doc_lines: + doc_lines = doc_lines[doc_lines.index("Parameters") :] + inner.__doc__ += "\n".join(doc_lines) + found_parameters = True + if not found_parameters: + inner.__doc__ += "See :class:`" + value_type.__name__ + "` for the available parameters.\n" + + parameters = [inspect.signature(inner).parameters["self"]] + parameters.extend(inspect.signature(value_type).parameters.values()) + inner.__signature__ = inspect.Signature(parameters, return_annotation=value_type) # type: ignore inner.__name__ = func_name inner.__qualname__ = f"{parent_class_name}.{func_name}" diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py index 95e176d96b..3254215084 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py @@ -23,6 +23,7 @@ from __future__ import annotations from collections.abc import Callable, Iterator +import inspect from typing import Any, Concatenate, Generic, TypeVar from grpc import Channel @@ -289,6 +290,10 @@ def inner(self: ParentT, /, *args: P.args, **kwargs: P.kwargs) -> CreatableValue # on the class itself, instead of the __init__ method. inner.__doc__ = object_class.__doc__ + parameters = [inspect.signature(inner).parameters["self"]] + parameters.extend(inspect.signature(object_class).parameters.values()) + inner.__signature__ = inspect.Signature(parameters, return_annotation=object_class) # type: ignore + inner.__name__ = func_name inner.__qualname__ = f"{parent_class_name}.{func_name}" inner.__module__ = module_name diff --git a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py index 2803e12d53..bd852527e7 100644 --- a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py @@ -24,7 +24,7 @@ from collections.abc import Callable import typing -from typing import Union +from typing import TypeAlias, Union from typing_extensions import Self @@ -52,16 +52,16 @@ # this would cause a circular import at run-time. from .boolean_selection_rule import BooleanSelectionRule - _LINKABLE_SELECTION_RULE_TYPES = Union[ - BooleanSelectionRule, - CutoffSelectionRule, - CylindricalSelectionRule, - GeometricalSelectionRule, - ParallelSelectionRule, - SphericalSelectionRule, - TubeSelectionRule, - VariableOffsetSelectionRule, - ] +_LINKABLE_SELECTION_RULE_TYPES: TypeAlias = Union[ + "BooleanSelectionRule", + CutoffSelectionRule, + CylindricalSelectionRule, + GeometricalSelectionRule, + ParallelSelectionRule, + SphericalSelectionRule, + TubeSelectionRule, + VariableOffsetSelectionRule, +] @mark_grpc_properties @@ -102,7 +102,7 @@ class LinkedSelectionRule(GenericEdgePropertyType): ====================================== ================================== =================== Note that :class:`.CutoffSelectionRule` and :class:`.BooleanSelectionRule` objects cannot be linked to - a Boolean Selection Rule, only to a Modeling Ply. + a Boolean Selection Rule, only to a Modeling Ply.. """ _SUPPORTED_SINCE = "24.2" diff --git a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py index b22fbb35c0..bd0735146e 100644 --- a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py +++ b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py @@ -85,15 +85,15 @@ # this would cause a circular import at run-time. from .. import BooleanSelectionRule, GeometricalSelectionRule - _SELECTION_RULES_LINKABLE_TO_OSS = typing.Union[ - ParallelSelectionRule, - CylindricalSelectionRule, - SphericalSelectionRule, - TubeSelectionRule, - GeometricalSelectionRule, - VariableOffsetSelectionRule, - BooleanSelectionRule, - ] +_SELECTION_RULES_LINKABLE_TO_OSS: typing.TypeAlias = typing.Union[ + ParallelSelectionRule, + CylindricalSelectionRule, + SphericalSelectionRule, + TubeSelectionRule, + "GeometricalSelectionRule", + VariableOffsetSelectionRule, + "BooleanSelectionRule", +] @dataclasses.dataclass diff --git a/src/ansys/acp/core/_tree_objects/sensor.py b/src/ansys/acp/core/_tree_objects/sensor.py index 0f1e2709c1..243dca0e48 100644 --- a/src/ansys/acp/core/_tree_objects/sensor.py +++ b/src/ansys/acp/core/_tree_objects/sensor.py @@ -22,7 +22,7 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from typing import Union, get_args from ansys.api.acp.v0 import sensor_pb2, sensor_pb2_grpc @@ -84,7 +84,7 @@ def __init__( name: str = "Sensor", sensor_type: SensorType = SensorType.SENSOR_BY_AREA, active: bool = True, - entities: Iterable[_LINKABLE_ENTITY_TYPES] = (), + entities: Sequence[_LINKABLE_ENTITY_TYPES] = (), ): super().__init__(name=name) self.active = active diff --git a/src/ansys/acp/core/_tree_objects/sublaminate.py b/src/ansys/acp/core/_tree_objects/sublaminate.py index ea3958d910..d900363280 100644 --- a/src/ansys/acp/core/_tree_objects/sublaminate.py +++ b/src/ansys/acp/core/_tree_objects/sublaminate.py @@ -24,7 +24,7 @@ from collections.abc import Callable, Iterable, Sequence import typing -from typing import Any, Union, get_args +from typing import Any, TypeAlias, Union, get_args from typing_extensions import Self @@ -51,7 +51,7 @@ __all__ = ["SubLaminate", "Lamina"] -_LINKABLE_MATERIAL_TYPES = Union[Fabric, Stackup] +_LINKABLE_MATERIAL_TYPES: TypeAlias = Union[Fabric, Stackup] @mark_grpc_properties From 291e2b49cec0daace82f27dcd3829c208a862e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Fri, 1 Nov 2024 06:51:51 +0100 Subject: [PATCH 36/96] Feat/add extrusion guide (#639) * add ExtrusionGuide * update API documentation --------- Co-authored-by: Dominik Gresch --- doc/source/api/enum_types.rst | 1 + doc/source/api/tree_objects.rst | 1 + src/ansys/acp/core/__init__.py | 4 + src/ansys/acp/core/_tree_objects/__init__.py | 4 + .../_grpc_helpers/property_helper.py | 31 ++- src/ansys/acp/core/_tree_objects/enums.py | 11 + .../acp/core/_tree_objects/extrusion_guide.py | 191 ++++++++++++++++++ .../acp/core/_tree_objects/solid_model.py | 12 ++ tests/unittests/test_extrusion_guide.py | 151 ++++++++++++++ 9 files changed, 396 insertions(+), 10 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/extrusion_guide.py create mode 100644 tests/unittests/test_extrusion_guide.py diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 5fd64ec923..6de7fed63a 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -18,6 +18,7 @@ Enumeration data types DropOffType EdgeSetType ElementalDataType + ExtrusionGuideType ExtrusionType ExtrusionMethodType FeFormat diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 98ffbf1b8b..50eb3f0a89 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -16,6 +16,7 @@ ACP objects DropOffSettings EdgeSet ElementSet + ExtrusionGuide ExportSettings Fabric GeometricalSelectionRule diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index ce7bf83441..817bedabc5 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -72,6 +72,8 @@ ElementSetElementalData, ElementSetNodalData, ExportSettings, + ExtrusionGuide, + ExtrusionGuideType, ExtrusionMethodType, ExtrusionType, Fabric, @@ -206,6 +208,8 @@ "ElementSetElementalData", "ElementSetNodalData", "example_helpers", + "ExtrusionGuide", + "ExtrusionGuideType", "ExportSettings", "ExtrusionMethodType", "ExtrusionType", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index be2b0054ff..49dd06f1ec 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -54,6 +54,7 @@ DropOffType, EdgeSetType, ElementalDataType, + ExtrusionGuideType, ExtrusionMethodType, ExtrusionType, GeometricalRuleType, @@ -84,6 +85,7 @@ UnitSystemType, VirtualGeometryDimension, ) +from .extrusion_guide import ExtrusionGuide from .fabric import Fabric from .geometrical_selection_rule import ( GeometricalSelectionRule, @@ -173,6 +175,8 @@ "ElementSetElementalData", "ElementSetNodalData", "ExportSettings", + "ExtrusionGuide", + "ExtrusionGuideType", "ExtrusionMethodType", "ExtrusionType", "Fabric", diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py index 52435ff4aa..f458ea9759 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/property_helper.py @@ -142,10 +142,20 @@ def inner(self: Readable) -> CreatableFromResourcePath | None: return inner +def _get_data_attribute(pb_obj: Message, name: str, check_optional: bool = False) -> _PROTOBUF_T: + name_parts = name.split(".") + if check_optional: + parent_obj = reduce(getattr, name_parts[:-1], pb_obj) + if hasattr(parent_obj, "HasField") and not parent_obj.HasField(name_parts[-1]): + return None + return reduce(getattr, name_parts, pb_obj) + + def grpc_data_getter( name: str, from_protobuf: _FROM_PROTOBUF_T[_GET_T], check_optional: bool = False, + getter_func: Callable[[Message, str, bool], _PROTOBUF_T] = _get_data_attribute, supported_since: str | None = None, ) -> Callable[[Readable], _GET_T]: """Create a getter method which obtains the server object via the gRPC Get endpoint. @@ -171,7 +181,7 @@ def grpc_data_getter( ) def inner(self: Readable) -> Any: self._get_if_stored() - pb_attribute = _get_data_attribute(self._pb_object, name, check_optional=check_optional) + pb_attribute = getter_func(self._pb_object, name, check_optional) if check_optional and pb_attribute is None: return None return from_protobuf(pb_attribute) @@ -193,15 +203,6 @@ def inner(self: Editable, value: Readable | None) -> None: return inner -def _get_data_attribute(pb_obj: Message, name: str, check_optional: bool = False) -> _PROTOBUF_T: - name_parts = name.split(".") - if check_optional: - parent_obj = reduce(getattr, name_parts[:-1], pb_obj) - if hasattr(parent_obj, "HasField") and not parent_obj.HasField(name_parts[-1]): - return None - return reduce(getattr, name_parts, pb_obj) - - def _set_data_attribute(pb_obj: ObjectInfo, name: str, value: _PROTOBUF_T) -> None: name_parts = name.split(".") @@ -268,6 +269,7 @@ def grpc_data_property( check_optional: bool = False, doc: str | None = None, setter_func: Callable[[ObjectInfo, str, _PROTOBUF_T], None] = _set_data_attribute, + getter_func: Callable[[Message, str, bool], _PROTOBUF_T] = _get_data_attribute, readable_since: str | None = None, writable_since: str | None = None, ) -> ReadWriteProperty[_GET_T, _SET_T]: @@ -292,6 +294,14 @@ def grpc_data_property( will be used. doc : Docstring for the property. + setter_func : + Function to set the property value. Can be customized to + implement additional checks or in case properties depend + on each other. + getter_func : + Function to get the property value. Can be customized to + implement additional checks or in case properties depend + on each other readable_since : Version since which the property is supported for reading. writable_since : @@ -310,6 +320,7 @@ def grpc_data_property( name, from_protobuf=from_protobuf, check_optional=check_optional, + getter_func=getter_func, supported_since=readable_since, ) ).setter( diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index 16f30a50cb..0a1914e2c7 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -26,6 +26,7 @@ drop_off_material_pb2, edge_set_pb2, enum_types_pb2, + extrusion_guide_pb2, geometrical_selection_rule_pb2, imported_modeling_ply_pb2, lookup_table_3d_pb2, @@ -56,6 +57,7 @@ "DropOffType", "EdgeSetType", "ElementalDataType", + "ExtrusionGuideType", "ExtrusionType", "ExtrusionMethodType", "GeometricalRuleType", @@ -472,6 +474,15 @@ ) ) +(ExtrusionGuideType, extrusion_guide_type_to_pb, extrusion_guide_type_from_pb) = ( + wrap_to_string_enum( + "ExtrusionGuideType", + extrusion_guide_pb2.ExtrusionGuideType, + module=__name__, + doc="Extrusion guide type used in an extrusion guide (solid model).", + ) +) + (OffsetDirectionType, offset_direction_type_to_pb, offset_direction_type_from_pb) = ( wrap_to_string_enum( "OffsetDirectionType", diff --git a/src/ansys/acp/core/_tree_objects/extrusion_guide.py b/src/ansys/acp/core/_tree_objects/extrusion_guide.py new file mode 100644 index 0000000000..77893a8388 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/extrusion_guide.py @@ -0,0 +1,191 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable +from functools import reduce + +from google.protobuf.message import Message + +from ansys.api.acp.v0 import extrusion_guide_pb2, extrusion_guide_pb2_grpc + +from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.property_helper import ( + _PROTOBUF_T, + _get_data_attribute, + _set_data_attribute, + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._grpc_helpers.protocols import ObjectInfo +from .base import CreatableTreeObject, IdTreeObject +from .edge_set import EdgeSet +from .enums import ( + ExtrusionGuideType, + extrusion_guide_type_from_pb, + extrusion_guide_type_to_pb, + status_type_from_pb, +) +from .object_registry import register +from .virtual_geometry import VirtualGeometry + +# Workaround: these imports are needed to make sphinx_autodoc_typehints understand +# the inherited members of the Elemental- and NodalData classes. +import numpy as np # noqa: F401 isort:skip + +__all__ = [ + "ExtrusionGuide", +] + + +@mark_grpc_properties +@register +class ExtrusionGuide(CreatableTreeObject, IdTreeObject): + """Instantiate an Extrusion Guide of a Solid Model. + + Parameters + ---------- + name : + The name of the Oriented Selection Set. + active : + Inactive extrusion guides are not used in the solid model extrusion. + edge_set : + Edge Set along which the Extrusion Guide acts. + extrusion_guide_type : + Type of the extrusion such as by direction or by geometry. + cad_geometry : + CAD geometry along which the extrusion guide runs. + Needed if the extrusion type is set to :attr:`ExtrusionGuideType.BY_GEOMETRY`. + direction : + Direction along which the extrusion guide runs. Need if + the extrusion type is set to :attr:`ExtrusionGuideType.BY_DIRECTION`. + radius : + Controls the sphere of influence for mesh morphing. + depth : + Defines the bias of the mesh morphing. + use_curvature_correction : + Apply a curvature correction during the solid model extrusion which results in + a smoother extruded surface. Under certain circumstances, deactivating + curvature correction can lead to better extrusion results. + + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "extrusion_guides" + _OBJECT_INFO_TYPE = extrusion_guide_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = extrusion_guide_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "ExtrusionGuide", + active: bool = True, + edge_set: EdgeSet | None = None, + extrusion_guide_type: ExtrusionGuideType = "by_direction", + cad_geometry: VirtualGeometry | None = None, + direction: tuple[float, float, float] = (0.0, 0.0, 1.0), + radius: float = 0.0, + depth: float = 0.0, + use_curvature_correction: bool = False, + ): + super().__init__(name=name) + self.active = active + self.edge_set = edge_set + self.extrusion_guide_type = ExtrusionGuideType(extrusion_guide_type) + self.cad_geometry = cad_geometry + self.direction = direction + self.radius = radius + self.depth = depth + self.use_curvature_correction = use_curvature_correction + + def _create_stub(self) -> extrusion_guide_pb2_grpc.ObjectServiceStub: + return extrusion_guide_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + + edge_set = grpc_link_property("properties.edge_set", allowed_types=(EdgeSet,)) + + extrusion_guide_type = grpc_data_property( + "properties.extrusion_guide_type", + from_protobuf=extrusion_guide_type_from_pb, + to_protobuf=extrusion_guide_type_to_pb, + ) + + cad_geometry = grpc_link_property("properties.cad_geometry", allowed_types=(VirtualGeometry,)) + + radius: ReadWriteProperty[float, float] = grpc_data_property("properties.radius") + + depth: ReadWriteProperty[float, float] = grpc_data_property("properties.depth") + + use_curvature_correction: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.use_curvature_correction" + ) + + # The extrusion guide type is not stored by the backend directly. Instead, + # it is derived from the property direction. Therefore, setting and getting + # the direction is blocked if the extrusion guide type is not `by_direction`. + # See Feature 1122546 in ADO. Once resolved, this check becomes obsolete + # but ensure backward compatibility. + @staticmethod + def _set_direction_attribute(pb_obj: ObjectInfo, name: str, value: _PROTOBUF_T) -> None: + # name is "properties.direction" + if ( + hasattr(pb_obj.properties, "extrusion_guide_type") + and getattr(pb_obj.properties, "extrusion_guide_type") + != extrusion_guide_pb2.BY_DIRECTION + ): + array = to_tuple_from_1D_array(value) + if array and sum(array) != 0: + raise RuntimeError( + "Cannot set direction if extrusion guide type is not 'by_direction'!" + ) + _set_data_attribute(pb_obj, name, value) + + @staticmethod + def _get_direction_attribute(pb_obj: Message, name: str, check_optional: bool) -> _PROTOBUF_T: + # name is "properties.direction" + name_parts = name.split(".") + parent_obj = reduce(getattr, name_parts[:-1], pb_obj) + if ( + hasattr(parent_obj, "extrusion_guide_type") + and getattr(parent_obj, "extrusion_guide_type") != extrusion_guide_pb2.BY_DIRECTION + ): + raise RuntimeError( + "Cannot access direction if the extrusion guide type is not 'by_direction'!" + ) + return _get_data_attribute(pb_obj, name, check_optional) + + direction = grpc_data_property( + "properties.direction", + from_protobuf=to_tuple_from_1D_array, + to_protobuf=to_1D_double_array, + setter_func=_set_direction_attribute, + getter_func=_get_direction_attribute, + ) diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index 4123352f7e..fd5a652dc5 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -27,6 +27,7 @@ from typing import Any from ansys.api.acp.v0 import ( + extrusion_guide_pb2_grpc, snap_to_geometry_pb2_grpc, solid_model_export_pb2, solid_model_pb2, @@ -72,6 +73,7 @@ solid_model_skin_export_format_to_pb, status_type_from_pb, ) +from .extrusion_guide import ExtrusionGuide from .material import Material from .modeling_ply import ModelingPly from .object_registry import register @@ -461,6 +463,16 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: drop_off_settings = nested_grpc_object_property("properties.drop_off_settings", DropOffSettings) export_settings = nested_grpc_object_property("properties.export_settings", ExportSettings) + create_extrusion_guide = define_create_method( + ExtrusionGuide, + func_name="create_extrusion_guide", + parent_class_name="SolidModel", + module_name=__module__, + ) + extrusion_guides = define_mutable_mapping( + ExtrusionGuide, extrusion_guide_pb2_grpc.ObjectServiceStub + ) + create_snap_to_geometry = define_create_method( SnapToGeometry, func_name="create_snap_to_geometry", diff --git a/tests/unittests/test_extrusion_guide.py b/tests/unittests/test_extrusion_guide.py new file mode 100644 index 0000000000..9bad92e411 --- /dev/null +++ b/tests/unittests/test_extrusion_guide.py @@ -0,0 +1,151 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import numpy.testing +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ExtrusionGuide, ExtrusionGuideType + +from .common.tree_object_tester import ObjectPropertiesToTest, TreeObjectTester, WithLockedMixin + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(ExtrusionGuide._SUPPORTED_SINCE): + pytest.skip("ExtrusionGuide is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def parent_object(model): + return model.create_solid_model() + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_extrusion_guide() + + +class TestExtrusionGuide(WithLockedMixin, TreeObjectTester): + COLLECTION_NAME = "extrusion_guides" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "active": True, + "edge_set": None, + "cad_geometry": None, + "direction": (0.0, 0.0, 1.0), + "radius": 0.0, + "depth": 0.0, + "use_curvature_correction": False, + "extrusion_guide_type": "by_direction", + } + + CREATE_METHOD_NAME = "create_extrusion_guide" + INITIAL_OBJECT_NAMES = tuple() + + @staticmethod + @pytest.fixture + def object_properties(model): + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_extrusion_guide"), + ("active", False), + ("edge_set", model.create_edge_set()), + ("extrusion_guide_type", ExtrusionGuideType.BY_DIRECTION), + ("cad_geometry", None), + ("direction", (2.0, 3.0, 4.0)), + ("radius", 1.5), + ("depth", 2.0), + ("use_curvature_correction", True), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) + + +def test_handling_of_extrusion_guide_type(model, parent_object, skip_before_version): + """Verify the handling of extrusion_guide_type. + + The backend determines the extrusion guide type based on the direction. + extrusion_guide_type = BY_DIRECTION if direction != 0., else + extrusion_guide_type = BY_GEOMETRY. + + In addition, the virtual geometry is None if + extrusion_guide_type = BY_DIRECTION. + """ + skip_before_version("25.1") + + virtual_cad = model.create_virtual_geometry(name="dummy") + + """ Case extrusion guide type = BY_DIRECTION """ + ex_by_direction = parent_object.create_extrusion_guide( + name="ExtrusionGuide", + direction=(0.0, 1.0, 1.0), + extrusion_guide_type=ExtrusionGuideType.BY_DIRECTION, + ) + assert ex_by_direction.extrusion_guide_type == ExtrusionGuideType.BY_DIRECTION + assert ex_by_direction.cad_geometry is None + numpy.testing.assert_allclose(ex_by_direction.direction, (0.0, 1.0, 1.0)) + # check that the user can modify the direction as long as the extrusion guide type is BY_DIRECTION + ex_by_direction.direction = (2.3, 2.0, 1.0) + # check that the user can change the extrusion guide type to BY_GEOMETRY + ex_by_direction.extrusion_guide_type = ExtrusionGuideType.BY_GEOMETRY + ex_by_direction.cad_geometry = virtual_cad + + """ Case extrusion guide type = BY_GEOMETRY """ + ex_by_geometry = parent_object.create_extrusion_guide( + name="ExtrusionGuide", + direction=(0.0, 0.0, 0.0), + extrusion_guide_type=ExtrusionGuideType.BY_GEOMETRY, + cad_geometry=virtual_cad, + ) + assert ex_by_geometry.extrusion_guide_type == ExtrusionGuideType.BY_GEOMETRY + assert ex_by_geometry.cad_geometry is not None and ex_by_geometry.cad_geometry.name == "dummy" + with pytest.raises(RuntimeError) as exc: + direction = ex_by_geometry.direction + assert "Cannot access direction if the extrusion guide type is not 'by_direction'!" in str( + exc.value + ) + + with pytest.raises(RuntimeError) as exc: + ex_by_geometry.direction = (0.0, 1.0, 1.0) + assert "Cannot set direction if extrusion guide type is not 'by_direction'!" in str(exc.value) + """ Case invalid initialization """ + with pytest.raises(RuntimeError) as exc: + ex_by_geometry = parent_object.create_extrusion_guide( + name="ExtrusionGuide", + direction=(0.0, 2.0, 1.0), + extrusion_guide_type=ExtrusionGuideType.BY_GEOMETRY, + cad_geometry=virtual_cad, + ) + assert "Cannot set direction if extrusion guide type is not 'by_direction'!" in str(exc.value) From cfd0575c2b79dbd9d386a12222ab93b2ac1d0d1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 07:34:53 +0100 Subject: [PATCH 37/96] Bump the dependencies group with 2 updates (#640) Bumps the dependencies group with 2 updates: [ansys-sphinx-theme](https://github.com/ansys/ansys-sphinx-theme) and [hypothesis](https://github.com/HypothesisWorks/hypothesis). Updates `ansys-sphinx-theme` from 1.1.7 to 1.2.0 - [Release notes](https://github.com/ansys/ansys-sphinx-theme/releases) - [Commits](https://github.com/ansys/ansys-sphinx-theme/compare/v1.1.7...v1.2.0) Updates `hypothesis` from 6.115.6 to 6.116.0 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.115.6...hypothesis-python-6.116.0) --- updated-dependencies: - dependency-name: ansys-sphinx-theme dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index ac9359173d..75cde9c92e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -489,25 +489,25 @@ clr-loader = ">=0.2.5,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.1.7" +version = "1.2.0" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.10" files = [ - {file = "ansys_sphinx_theme-1.1.7-py3-none-any.whl", hash = "sha256:fb67187650865e068d09d20a211e995a4b00e2d0dc2c67bd769811b438147663"}, - {file = "ansys_sphinx_theme-1.1.7.tar.gz", hash = "sha256:7f0bd36482b538fa76cf9d671ca3faa8a96d35f3ebc54674a6d1db73e8e9dc1d"}, + {file = "ansys_sphinx_theme-1.2.0-py3-none-any.whl", hash = "sha256:2af3824e058762f9d8c73c49082fcd6d79d3961f8949bf24ee27d3a71ca0e957"}, + {file = "ansys_sphinx_theme-1.2.0.tar.gz", hash = "sha256:1d296945ebfaeb478f7a48967c5de6741b3f762f04bd968ea2e20727d53dc63f"}, ] [package.dependencies] importlib-metadata = ">=4.0" Jinja2 = ">=3.1.2" pdf2image = ">=1.17.0" -pydata-sphinx-theme = ">=0.15.4,<0.16" +pydata-sphinx-theme = ">=0.15.4,<0.17" Sphinx = ">=4.2.0" [package.extras] -autoapi = ["sphinx-autoapi (==3.3.2)", "sphinx-design (==0.6.1)", "sphinx-jinja (==2.0.2)"] -doc = ["Pillow (>=9.0)", "PyGitHub (==2.4.0)", "Sphinx (==8.0.2)", "jupytext (==1.16.4)", "nbsphinx (==0.9.5)", "notebook (==7.2.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "pyvista[jupyter] (==0.44.1)", "requests (==2.32.3)", "sphinx-autoapi (==3.3.2)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-gallery (==0.17.1)", "sphinx-jinja (==2.0.2)", "sphinx-notfound-page (==1.0.4)"] +autoapi = ["sphinx-autoapi (==3.3.3)", "sphinx-design (==0.6.1)", "sphinx-jinja (==2.0.2)"] +doc = ["Pillow (>=9.0)", "PyGitHub (==2.4.0)", "Sphinx (==8.1.3)", "jupytext (==1.16.4)", "nbsphinx (==0.9.5)", "notebook (==7.2.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "pyvista[jupyter] (==0.44.1)", "requests (==2.32.3)", "sphinx-autoapi (==3.3.3)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-gallery (==0.18.0)", "sphinx-jinja (==2.0.2)", "sphinx-notfound-page (==1.0.4)"] [[package]] name = "ansys-tools-filetransfer" @@ -1904,13 +1904,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.115.6" +version = "6.116.0" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.115.6-py3-none-any.whl", hash = "sha256:d7b7173934753b9624680b38a85749de4fce367c44acb36c08b62765cc0a7a19"}, - {file = "hypothesis-6.115.6.tar.gz", hash = "sha256:d4db48eef183591085676783967e943bb89fef4d596f78c3e4116c61fcc63a6b"}, + {file = "hypothesis-6.116.0-py3-none-any.whl", hash = "sha256:d30271214eae0d4758b72b408e9777405c7c7f687e14e8a42853adea887b2891"}, + {file = "hypothesis-6.116.0.tar.gz", hash = "sha256:9c1ac9a2edb77aacae1950d8ded6b3f40dbf8483097c88336265c348d2132c71"}, ] [package.dependencies] From ff53ac77433df3385dbab27b01a378d6687452e6 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 4 Nov 2024 13:14:33 +0100 Subject: [PATCH 38/96] Fix teardown of direct launcher when start fails (#641) The direct launcher '.stop()' errored when '.start()' had failed, since the '_process' attribute was not yet defined. This caused the error message to be misleading, as it was pointing to the error in '.stop()' first, instead of the actual error in '.start()'. This is fixed by explicitly setting '_process' to None in the constructor of the DirectLauncher class, and checking if it is not None before trying to stop it. --- src/ansys/acp/core/_server/direct.py | 5 ++- tests/unittests/test_direct_launcher.py | 50 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/unittests/test_direct_launcher.py diff --git a/src/ansys/acp/core/_server/direct.py b/src/ansys/acp/core/_server/direct.py index a28b6319eb..6d1bdde5a9 100644 --- a/src/ansys/acp/core/_server/direct.py +++ b/src/ansys/acp/core/_server/direct.py @@ -95,7 +95,7 @@ class DirectLauncher(LauncherProtocol[DirectLaunchConfig]): def __init__(self, *, config: DirectLaunchConfig): self._config = config self._url: str - self._process: subprocess.Popen[str] + self._process: subprocess.Popen[str] | None = None self._stdout: TextIO self._stderr: TextIO @@ -123,6 +123,9 @@ def start(self) -> None: ) def stop(self, *, timeout: float | None = None) -> None: + if self._process is None: + # The process has not been started, and therefore doesn't need to be stopped + return self._process.terminate() try: self._process.wait(timeout=timeout) diff --git a/tests/unittests/test_direct_launcher.py b/tests/unittests/test_direct_launcher.py new file mode 100644 index 0000000000..ee10c1c4f7 --- /dev/null +++ b/tests/unittests/test_direct_launcher.py @@ -0,0 +1,50 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import pytest + +import ansys.acp.core as pyacp +from ansys.acp.core._server.direct import DirectLauncher + + +def test_inexistent_binary_error(): + """Check that the right error is raised when the binary does not exist.""" + with pytest.raises(FileNotFoundError) as exc: + pyacp.launch_acp( + launch_mode=pyacp.LaunchMode.DIRECT, + config=pyacp.DirectLaunchConfig(binary_path="inexistent_path"), + ) + assert "Binary not found" in str(exc.value) + + +def test_stop_after_failed_start(): + """Check that the .stop() method works after a failed start. + + This test is necessary because the '.stop()' method is called on teardown + even if the '.start()' method fails. + In the 'test_inexistent_binary_error' test, errors in '.stop()' are not + captured because they happen after the test ends. + """ + launcher = DirectLauncher(config=pyacp.DirectLaunchConfig(binary_path="inexistent_path")) + with pytest.raises(FileNotFoundError): + launcher.start() + launcher.stop() From 20cdf5a24a6e487fea1b834b39e79644c60cae41 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 5 Nov 2024 08:00:19 +0100 Subject: [PATCH 39/96] Accept kwargs in ACPWorkflow.from_cdb_or_dat_file (#645) Accept keyword arguments in ACPWorkflow.from_cdb_or_dat_file, to pass to ACP.import_model. This allows setting the `ignored_enties`, and `convert_section_data` parameters. --- src/ansys/acp/core/_workflow.py | 21 +++++++++++++++++---- tests/unittests/test_workflow.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/ansys/acp/core/_workflow.py b/src/ansys/acp/core/_workflow.py index 59ec519761..f108b42646 100644 --- a/src/ansys/acp/core/_workflow.py +++ b/src/ansys/acp/core/_workflow.py @@ -215,8 +215,9 @@ def from_cdb_or_dat_file( *, acp: ACP[ServerProtocol], cdb_or_dat_file_path: PATH, - unit_system: UnitSystemType = UnitSystemType.UNDEFINED, + unit_system: UnitSystemType = UnitSystemType.FROM_FILE, local_working_directory: PATH | None = None, + **kwargs: Any, ) -> "ACPWorkflow": """Instantiate an ACP Workflow from a cdb file. @@ -227,11 +228,22 @@ def from_cdb_or_dat_file( cdb_or_dat_file_path: The path to the cdb or dat file. unit_system: - Has to be ``UnitSystemType.UNDEFINED`` if the unit system - is specified in the cdb or dat file. Needs to be set to a defined unit system - if the unit system is not set in the cdb or dat file. + Defines the unit system of the imported file. Must be set if the + input file does not have units. If the input file does have units, + ``unit_system`` must be either ``"from_file"``, or match the input + unit system. local_working_directory: The local working directory. If None, a temporary directory will be created. + ignored_entities: + Entities to ignore when loading the FE file. Can be a subset of + the following values: + ``"coordinate_systems"``, ``"element_sets"``, ``"materials"``, + ``"mesh"``, or ``"shell_sections"``. + Available only when the format is not ``"acp:h5"``. + convert_section_data: + Whether to import the section data of a shell model and convert it + into ACP composite definitions. + Available only when the format is not ``"acp:h5"``. """ instance = cls( @@ -240,6 +252,7 @@ def from_cdb_or_dat_file( local_working_directory=local_working_directory, file_format="ansys:cdb", unit_system=unit_system, + **kwargs, ) if instance.model.unit_system == UnitSystemType.UNDEFINED: diff --git a/tests/unittests/test_workflow.py b/tests/unittests/test_workflow.py index ce237e8d5f..15bb720fc4 100644 --- a/tests/unittests/test_workflow.py +++ b/tests/unittests/test_workflow.py @@ -154,3 +154,20 @@ def test_workflow_unit_system_cdb(acp_instance, model_data_dir, unit_system): unit_system=unit_system, ) assert workflow.model.unit_system == unit_system + + +def test_workflow_ignored_entities(acp_instance, model_data_dir): + """Test that workflow the keyword argument for ignored entities.""" + input_file_path = model_data_dir / "minimal_model_2.cdb" + + kwargs = { + "acp": acp_instance, + "cdb_or_dat_file_path": input_file_path, + "unit_system": UnitSystemType.MPA, + } + + workflow_without_ignored_entities = ACPWorkflow.from_cdb_or_dat_file(**kwargs) + assert len(workflow_without_ignored_entities.model.rosettes) > 0 + + workflow = ACPWorkflow.from_cdb_or_dat_file(ignored_entities=["coordinate_systems"], **kwargs) + assert len(workflow.model.rosettes) == 0 From 2d726a8a7e0159db7ea9c070e3e718b7f3e85380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:08:00 +0100 Subject: [PATCH 40/96] Feat/586 add field definition (#644) * add FieldDefinition and add according unit tests --------- Co-authored-by: Dominik Gresch --- doc/source/api/tree_objects.rst | 1 + doc/source/conf.py | 4 + src/ansys/acp/core/__init__.py | 2 + src/ansys/acp/core/_tree_objects/__init__.py | 2 + .../core/_tree_objects/field_definition.py | 148 ++++++++++++++++++ src/ansys/acp/core/_tree_objects/model.py | 12 ++ tests/unittests/test_field_definition.py | 115 ++++++++++++++ 7 files changed, 284 insertions(+) create mode 100644 src/ansys/acp/core/_tree_objects/field_definition.py create mode 100644 tests/unittests/test_field_definition.py diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 50eb3f0a89..ac28a9f033 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -19,6 +19,7 @@ ACP objects ExtrusionGuide ExportSettings Fabric + FieldDefinition GeometricalSelectionRule ImportedModelingGroup ImportedModelingPly diff --git a/doc/source/conf.py b/doc/source/conf.py index f3eababf2a..17e37cb93a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -40,6 +40,9 @@ def _signature( ) # Import type aliases so that they can be resolved correctly. + from ansys.acp.core._tree_objects.field_definition import ( # noqa: F401 + _SCOPE_ENTITIES_LINKABLE_TO_FIELD_DEFINITION, + ) from ansys.acp.core._tree_objects.linked_selection_rule import ( # noqa: F401 _LINKABLE_SELECTION_RULE_TYPES, ) @@ -66,6 +69,7 @@ def _signature( # 'inspect.signature' from performing the 'eval'. for i, param in enumerate(parameters): if param.annotation in [ + "Sequence[_SCOPE_ENTITIES_LINKABLE_TO_FIELD_DEFINITION]", "Sequence[_SELECTION_RULES_LINKABLE_TO_OSS]", "Sequence[_LINKABLE_ENTITY_TYPES]", "_LINKABLE_MATERIAL_TYPES", diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 817bedabc5..4ab46a27bd 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -79,6 +79,7 @@ Fabric, FabricWithAngle, FeFormat, + FieldDefinition, GeometricalRuleType, GeometricalSelectionRule, GeometricalSelectionRuleElementalData, @@ -215,6 +216,7 @@ "ExtrusionType", "Fabric", "FabricWithAngle", + "FieldDefinition", "FeFormat", "GeometricalRuleType", "GeometricalSelectionRule", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 49dd06f1ec..f8e409d206 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -87,6 +87,7 @@ ) from .extrusion_guide import ExtrusionGuide from .fabric import Fabric +from .field_definition import FieldDefinition from .geometrical_selection_rule import ( GeometricalSelectionRule, GeometricalSelectionRuleElementalData, @@ -181,6 +182,7 @@ "ExtrusionType", "Fabric", "FabricWithAngle", + "FieldDefinition", "FeFormat", "FieldVariable", "GeometricalRuleType", diff --git a/src/ansys/acp/core/_tree_objects/field_definition.py b/src/ansys/acp/core/_tree_objects/field_definition.py new file mode 100644 index 0000000000..43c5d6bd8f --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/field_definition.py @@ -0,0 +1,148 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable, Sequence +import typing + +from ansys.api.acp.v0 import field_definition_pb2, field_definition_pb2_grpc + +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.linked_object_list import define_polymorphic_linked_object_list +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .element_set import ElementSet +from .enums import status_type_from_pb +from .lookup_table_1d_column import LookUpTable1DColumn +from .lookup_table_3d_column import LookUpTable3DColumn +from .modeling_ply import ModelingPly +from .object_registry import register +from .oriented_selection_set import OrientedSelectionSet + +__all__ = ["FieldDefinition"] + + +_SCOPE_ENTITIES_LINKABLE_TO_FIELD_DEFINITION: typing.TypeAlias = typing.Union[ + ElementSet, + OrientedSelectionSet, + ModelingPly, +] + + +@mark_grpc_properties +@register +class FieldDefinition(CreatableTreeObject, IdTreeObject): + """Instantiate a Field Definition. + + A field definition is used to configure the state of variable materials. + For instance, a Lookup Table can be used to define the distribution of a + state of material field (e.g. degradation). The field definition allows + to define the material field per element or per ply and element. + + Note + ---- + + Field definitions are currently only supported through (Py)Mechanical. + The direct interface of PyACP to (Py)MADL ignores field definitions. + + Parameters + ---------- + name : + Name of the field definition. + active : + Inactive field definitions are ignored. + field_variable_name : + Links the material field to a field variable name. + The field variable name must be defined in the material properties. + Note that the ``Temperature`` and ``Shear Angle`` field variables are not available + for Field Definitions. Temperature is defined through the solution and `Shear Angle` + is defined via draping calculations. + scope_entities : + Define the scope of the field definition. You can use a combination + of Element Sets and Oriented Selection Sets for elemental scope. + For ply-wise field definitions, a combination of Modeling Plies can be selected. + Note that the field definition is only applied to the Analysis Plies attached to + the Modeling Plies. A combination of elemental and ply-wise definition is not supported. + scalar_field : + Select the scalar Look-Up Table column from which the state of the field + variable is interpolated from. + full_mapping : + Specify to include the shell offset of each analysis ply for the interpolation process. + The default is to interpolate the state of the field variables at the shell element centroid. + For solid elements, the position of each analysis ply is automatically considered. + + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "field_definitions" + _OBJECT_INFO_TYPE = field_definition_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = field_definition_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "FieldDefinition", + active: bool = True, + field_variable_name: str = "", + scope_entities: Sequence[_SCOPE_ENTITIES_LINKABLE_TO_FIELD_DEFINITION] = tuple(), + scalar_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, + full_mapping: bool = False, + ): + super().__init__(name=name) + + self.active = active + self.field_variable_name = field_variable_name + self.scope_entities = scope_entities + self.scalar_field = scalar_field + self.full_mapping = full_mapping + + def _create_stub(self) -> field_definition_pb2_grpc.ObjectServiceStub: + return field_definition_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + field_variable_name: ReadWriteProperty[str, str] = grpc_data_property( + "properties.field_variable_name" + ) + + scope_entities = define_polymorphic_linked_object_list( + "properties.scope_entities", + allowed_types=( + ElementSet, + OrientedSelectionSet, + ModelingPly, + ), + ) + + scalar_field = grpc_link_property( + "properties.scalar_field", allowed_types=(LookUpTable1DColumn, LookUpTable3DColumn) + ) + + full_mapping: ReadWriteProperty[bool, bool] = grpc_data_property("properties.full_mapping") diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 3c99c2d562..14a2bc22db 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -41,6 +41,7 @@ element_set_pb2_grpc, enum_types_pb2, fabric_pb2_grpc, + field_definition_pb2_grpc, geometrical_selection_rule_pb2_grpc, imported_modeling_group_pb2_grpc, lookup_table_1d_pb2_grpc, @@ -114,6 +115,7 @@ unit_system_type_to_pb, ) from .fabric import Fabric +from .field_definition import FieldDefinition from .geometrical_selection_rule import GeometricalSelectionRule from .imported_modeling_group import ImportedModelingGroup from .lookup_table_1d import LookUpTable1D @@ -758,6 +760,16 @@ def export_modeling_ply_geometries( ) sensors = define_mutable_mapping(Sensor, sensor_pb2_grpc.ObjectServiceStub) + create_field_definition = define_create_method( + FieldDefinition, + func_name="create_field_definition", + parent_class_name="Model", + module_name=__module__, + ) + field_definitions = define_mutable_mapping( + FieldDefinition, field_definition_pb2_grpc.ObjectServiceStub + ) + @property def mesh(self) -> MeshData: """Mesh on which the model is defined.""" diff --git a/tests/unittests/test_field_definition.py b/tests/unittests/test_field_definition.py new file mode 100644 index 0000000000..1dd1dd8be2 --- /dev/null +++ b/tests/unittests/test_field_definition.py @@ -0,0 +1,115 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import FieldDefinition + +from .common.linked_object_list_tester import LinkedObjectListTestCase, LinkedObjectListTester +from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(FieldDefinition._SUPPORTED_SINCE): + pytest.skip("FieldDefinition is not supported on this version of the server.") + + +@pytest.fixture +def parent_object(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_field_definition() + + +class TestFieldDefinition(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "field_definitions" + + @staticmethod + @pytest.fixture + def default_properties(acp_instance): + return { + "status": "NOTUPTODATE", + "active": True, + "scalar_field": None, + "scope_entities": tuple(), + "full_mapping": False, + } + + CREATE_METHOD_NAME = "create_field_definition" + + @staticmethod + @pytest.fixture + def object_properties(parent_object, acp_instance): + model = parent_object + lut_3D = model.create_lookup_table_3d(name="LUT 3D") + lut_col = lut_3D.create_column(name="Column 1") + el_set = model.create_element_set(name="my element set") + mg = model.create_modeling_group(name="my modeling group") + modeling_ply = mg.create_modeling_ply(name="my Modeling Ply") + oss = model.create_oriented_selection_set(name="my OSS") + + return ObjectPropertiesToTest( + read_write=[ + ("name", "FD name"), + ("active", False), + ("scalar_field", lut_col), + ("scope_entities", [el_set, modeling_ply, oss]), + ("full_mapping", True), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) + + +@pytest.fixture +def linked_object_case(tree_object, parent_object): + return LinkedObjectListTestCase( + parent_object=tree_object, + linked_attribute_name="scope_entities", + existing_linked_object_names=(), + linked_object_constructor=parent_object.create_element_set, + ) + + +linked_object_case_empty = linked_object_case + + +@pytest.fixture +def linked_object_case_nonempty(tree_object, parent_object): + tree_object.scope_entities = [parent_object.create_oriented_selection_set(name="OSS.1")] + return LinkedObjectListTestCase( + parent_object=tree_object, + linked_attribute_name="scope_entities", + existing_linked_object_names=("OSS.1",), + linked_object_constructor=parent_object.create_oriented_selection_set, + ) + + +class TestLinkedObjectLists(LinkedObjectListTester): + pass From e341b87cf25901fdaf6cdbe420cae2472f1fcfaa Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 5 Nov 2024 11:49:54 +0100 Subject: [PATCH 41/96] Implement ImportedSolidModel object (#638) Add exposure for the `ImportedSolidModel` object. Move the solid model export methods `export` and `export_skin` to a mixin class and re-use them here. Add elemental and nodal data attributes on both the regular and imported solid model. Rename `ExportSettings` to `SolidModelExportSettings`, and add a separate `ImportedSolidModelExportSettings`, since not all options are supported for imported solid models. Closes #591 --- doc/source/api/enum_types.rst | 1 + doc/source/api/internal.rst | 3 +- doc/source/api/mesh_data.rst | 4 + doc/source/api/tree_objects.rst | 6 +- doc/source/conf.py | 4 + doc/source/index.rst | 2 +- src/ansys/acp/core/__init__.py | 22 +- src/ansys/acp/core/_tree_objects/__init__.py | 24 +- .../core/_tree_objects/_solid_model_export.py | 86 +++++ .../_tree_objects/imported_solid_model.py | 328 ++++++++++++++++++ src/ansys/acp/core/_tree_objects/model.py | 12 + .../acp/core/_tree_objects/solid_model.py | 91 ++--- tests/conftest.py | 18 + ...minimal_complete_model_no_matml_link.acph5 | Bin 138204 -> 142889 bytes tests/unittests/test_imported_solid_model.py | 272 +++++++++++++++ tests/unittests/test_solid_model.py | 30 +- 16 files changed, 837 insertions(+), 66 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/_solid_model_export.py create mode 100644 src/ansys/acp/core/_tree_objects/imported_solid_model.py create mode 100644 tests/unittests/test_imported_solid_model.py diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 6de7fed63a..906f313a49 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -44,6 +44,7 @@ Enumeration data types SectionCutType SensorType SolidModelExportFormat + SolidModelImportFormat SolidModelSkinExportFormat StatusType SymmetryType diff --git a/doc/source/api/internal.rst b/doc/source/api/internal.rst index 100101d121..81ad1cc1b5 100644 --- a/doc/source/api/internal.rst +++ b/doc/source/api/internal.rst @@ -28,10 +28,11 @@ Internal objects _tree_objects._grpc_helpers.protocols.ObjectInfo _tree_objects._mesh_data.MeshDataT _tree_objects._mesh_data.ScalarDataT + _tree_objects._solid_model_export.SolidModelExportMixin _tree_objects.base.CreatableTreeObject + _tree_objects.base.ServerWrapper _tree_objects.base.TreeObject _tree_objects.base.TreeObjectBase - _tree_objects.base.ServerWrapper _tree_objects.material.property_sets.wrapper.TC _tree_objects.material.property_sets.wrapper.TV _workflow._LocalWorkingDir diff --git a/doc/source/api/mesh_data.rst b/doc/source/api/mesh_data.rst index 18fd954363..e225b06cf6 100644 --- a/doc/source/api/mesh_data.rst +++ b/doc/source/api/mesh_data.rst @@ -18,6 +18,8 @@ Mesh data objects ElementSetNodalData GeometricalSelectionRuleElementalData GeometricalSelectionRuleNodalData + ImportedSolidModelElementalData + ImportedSolidModelNodalData MeshData ModelElementalData ModelingPlyElementalData @@ -30,6 +32,8 @@ Mesh data objects ProductionPlyElementalData ProductionPlyNodalData ScalarData + SolidModelElementalData + SolidModelNodalData SphericalSelectionRuleElementalData SphericalSelectionRuleNodalData TriangleMesh diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index ac28a9f033..6e119f3154 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -17,14 +17,15 @@ ACP objects EdgeSet ElementSet ExtrusionGuide - ExportSettings Fabric FieldDefinition GeometricalSelectionRule + ImportedAnalysisPly ImportedModelingGroup ImportedModelingPly ImportedProductionPly - ImportedAnalysisPly + ImportedSolidModel + ImportedSolidModelExportSettings InterfaceLayer LookUpTable1D LookUpTable1DColumn @@ -43,6 +44,7 @@ ACP objects Sensor SnapToGeometry SolidModel + SolidModelExportSettings SphericalSelectionRule Stackup SubLaminate diff --git a/doc/source/conf.py b/doc/source/conf.py index 17e37cb93a..c2874f921d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -26,8 +26,12 @@ def _signature( # Import classes which were guarded with a 'typing.TYPE_CHECKING' explicitly here, otherwise # the 'eval' in 'inspect.signature' will fail. + # Some imports are needed because these types occur in a dataclass base class, which is + # not in the same module as the documented class. from collections.abc import Sequence # noqa: F401 + import numpy as np # noqa: F401 + from ansys.acp.core import ( # noqa: F401 BooleanSelectionRule, CADComponent, diff --git a/doc/source/index.rst b/doc/source/index.rst index 3903b3d4bc..b4664d478b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -76,8 +76,8 @@ optimization of composite structures. Limitations ^^^^^^^^^^^ -* Only shell workflows are supported, solid models can not yet be defined using PyACP * FieldDefinitions for variable material properties are not supported * Visualization and mesh data of imported plies are not supported yet * Section cuts cannot be visualized * Sampling point analysis data is not available +* Imported solid model mapping statistics are not available diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 4ab46a27bd..b58b593551 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -71,7 +71,6 @@ ElementSet, ElementSetElementalData, ElementSetNodalData, - ExportSettings, ExtrusionGuide, ExtrusionGuideType, ExtrusionMethodType, @@ -92,6 +91,10 @@ ImportedPlyOffsetType, ImportedPlyThicknessType, ImportedProductionPly, + ImportedSolidModel, + ImportedSolidModelElementalData, + ImportedSolidModelExportSettings, + ImportedSolidModelNodalData, InterfaceLayer, IntersectionType, Lamina, @@ -140,7 +143,11 @@ SensorType, SnapToGeometry, SolidModel, + SolidModelElementalData, SolidModelExportFormat, + SolidModelExportSettings, + SolidModelImportFormat, + SolidModelNodalData, SolidModelSkinExportFormat, SphericalSelectionRule, SphericalSelectionRuleElementalData, @@ -211,7 +218,6 @@ "example_helpers", "ExtrusionGuide", "ExtrusionGuideType", - "ExportSettings", "ExtrusionMethodType", "ExtrusionType", "Fabric", @@ -228,12 +234,16 @@ "get_model_tree", "IgnorableEntity", "ImportedAnalysisPly", - "ImportedProductionPly", - "ImportedModelingPly", "ImportedModelingGroup", + "ImportedModelingPly", "ImportedPlyDrapingType", "ImportedPlyOffsetType", "ImportedPlyThicknessType", + "ImportedProductionPly", + "ImportedSolidModel", + "ImportedSolidModelElementalData", + "ImportedSolidModelExportSettings", + "ImportedSolidModelNodalData", "InterfaceLayer", "IntersectionType", "Lamina", @@ -288,7 +298,11 @@ "SensorType", "SnapToGeometry", "SolidModel", + "SolidModelElementalData", "SolidModelExportFormat", + "SolidModelExportSettings", + "SolidModelImportFormat", + "SolidModelNodalData", "SolidModelSkinExportFormat", "SphericalSelectionRule", "SphericalSelectionRuleElementalData", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index f8e409d206..5e8f17df82 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -97,6 +97,13 @@ from .imported_modeling_group import ImportedModelingGroup from .imported_modeling_ply import ImportedModelingPly from .imported_production_ply import ImportedProductionPly +from .imported_solid_model import ( + ImportedSolidModel, + ImportedSolidModelElementalData, + ImportedSolidModelExportSettings, + ImportedSolidModelNodalData, + SolidModelImportFormat, +) from .interface_layer import InterfaceLayer from .linked_selection_rule import LinkedSelectionRule from .lookup_table_1d import LookUpTable1D @@ -123,7 +130,13 @@ from .section_cut import SectionCut from .sensor import Sensor from .snap_to_geometry import SnapToGeometry -from .solid_model import DropOffSettings, ExportSettings, SolidModel +from .solid_model import ( + DropOffSettings, + SolidModel, + SolidModelElementalData, + SolidModelExportSettings, + SolidModelNodalData, +) from .spherical_selection_rule import ( SphericalSelectionRule, SphericalSelectionRuleElementalData, @@ -175,7 +188,6 @@ "ElementSet", "ElementSetElementalData", "ElementSetNodalData", - "ExportSettings", "ExtrusionGuide", "ExtrusionGuideType", "ExtrusionMethodType", @@ -197,6 +209,10 @@ "ImportedPlyOffsetType", "ImportedPlyThicknessType", "ImportedProductionPly", + "ImportedSolidModel", + "ImportedSolidModelElementalData", + "ImportedSolidModelExportSettings", + "ImportedSolidModelNodalData", "InterfaceLayer", "InterpolationOptions", "IntersectionType", @@ -247,7 +263,11 @@ "SensorType", "SnapToGeometry", "SolidModel", + "SolidModelElementalData", "SolidModelExportFormat", + "SolidModelExportSettings", + "SolidModelImportFormat", + "SolidModelNodalData", "SolidModelSkinExportFormat", "SphericalSelectionRule", "SphericalSelectionRuleElementalData", diff --git a/src/ansys/acp/core/_tree_objects/_solid_model_export.py b/src/ansys/acp/core/_tree_objects/_solid_model_export.py new file mode 100644 index 0000000000..659e999c85 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/_solid_model_export.py @@ -0,0 +1,86 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +import typing + +from ansys.api.acp.v0 import solid_model_export_pb2 + +from .._typing_helper import PATH as _PATH +from .._utils.path_to_str import path_to_str_checked +from ._grpc_helpers.exceptions import wrap_grpc_errors +from .base import CreatableTreeObject +from .enums import ( + SolidModelExportFormat, + SolidModelSkinExportFormat, + solid_model_export_format_to_pb, + solid_model_skin_export_format_to_pb, +) + +__all__ = ["SolidModelExportMixin"] + + +class SolidModelExportMixin(CreatableTreeObject): + """Mixin class for adding export functionality to the solid model and imported solid model classes.""" + + def export(self, *, path: _PATH, format: SolidModelExportFormat) -> None: + """Export the solid model to a file. + + Parameters + ---------- + path : + Path to the file where the solid model is saved. + format : + Format of the exported file. Available formats are ``"ansys:h5"`` + and ``"ansys:cdb"``. + + """ + with wrap_grpc_errors(): + self._get_stub().ExportToFile( # type: ignore + solid_model_export_pb2.ExportToFileRequest( + resource_path=self._resource_path, + path=path_to_str_checked(path), + format=typing.cast(typing.Any, solid_model_export_format_to_pb(format)), + ) + ) + + def export_skin(self, *, path: _PATH, format: SolidModelSkinExportFormat) -> None: + """Export the skin of the solid model to a file. + + Parameters + ---------- + path : + Path to the file where the solid model skin is saved. + format : + Format of the exported file. Available formats are ``"ansys:cdb"``, + ``"step"``, ``"iges"``, and ``"stl"``. + + """ + with wrap_grpc_errors(): + self._get_stub().ExportSkin( # type: ignore + solid_model_export_pb2.ExportSkinRequest( + resource_path=self._resource_path, + path=path_to_str_checked(path), + format=typing.cast(typing.Any, solid_model_skin_export_format_to_pb(format)), + ) + ) diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py new file mode 100644 index 0000000000..ed4e8b1baf --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -0,0 +1,328 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable +import dataclasses +from typing import Any + +from ansys.api.acp.v0 import ( + enum_types_pb2, + imported_solid_model_pb2, + imported_solid_model_pb2_grpc, + solid_model_pb2, +) + +from .._typing_helper import PATH as _PATH +from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from ._grpc_helpers.enum_wrapper import wrap_to_string_enum +from ._grpc_helpers.exceptions import wrap_grpc_errors +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._solid_model_export import SolidModelExportMixin +from .base import ( + CreatableTreeObject, + IdTreeObject, + TreeObjectAttributeWithCache, + nested_grpc_object_property, +) +from .enums import ( + UnitSystemType, + status_type_from_pb, + unit_system_type_from_pb, + unit_system_type_to_pb, +) +from .material import Material +from .object_registry import register + +__all__ = [ + "ImportedSolidModel", + "SolidModelImportFormat", + "ImportedSolidModelExportSettings", + "ImportedSolidModelElementalData", + "ImportedSolidModelNodalData", +] + + +SolidModelImportFormat, solid_model_import_format_to_pb, solid_model_import_format_from_pb = ( + wrap_to_string_enum( + "SolidModelImportFormat", + enum_types_pb2.FileFormat, + module=__name__, + value_converter=lambda val: val.lower().replace("_", ":"), + doc="Options for the file format when importing a solid model.", + explicit_value_list=( + enum_types_pb2.FileFormat.ANSYS_H5, + enum_types_pb2.FileFormat.ANSYS_CDB, + enum_types_pb2.FileFormat.ANSYS_DAT, + ), + ) +) + + +@dataclasses.dataclass +class ImportedSolidModelElementalData(ElementalData): + """Represents elemental data for an imported solid model.""" + + +@dataclasses.dataclass +class ImportedSolidModelNodalData(NodalData): + """Represents nodal data for an imported solid model.""" + + +@mark_grpc_properties +class ImportedSolidModelExportSettings(TreeObjectAttributeWithCache): + """Defines the settings for exporting an imported solid model. + + Parameters + ---------- + use_default_section_index : + Use the default start index for sections. + section_index : + Custom start index for sections. + Only used if ``use_default_section_index`` is False. + use_default_coordinate_system_index : + Use the default start index for coordinate systems. + coordinate_system_index : + Custom start index for coordinate systems. + Only used if ``use_default_coordinate_system_index`` is False. + use_default_material_index : + Use the default start index for materials. + material_index : + Custom start index for materials. + Only used if ``use_default_material_index`` is False. + use_default_node_index : + Use the default start index for nodes. + node_index : + Custom start index for nodes. + Only used if ``use_default_node_index`` is False. + use_default_element_index : + Use the default start index for elements. + element_index : + Custom start index for elements. + Only used if ``use_default_element_index`` is False. + use_solsh_elements : + When True, export linear layered elements as Solsh (Solid190). + drop_hanging_nodes : + When True, the hanging nodes of quadratic solid meshes are dropped. + use_solid_model_prefix : + Use the imported solid model name as a prefix for the exported file. + + """ + + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + use_default_section_index: bool = True, + section_index: int = 0, + use_default_coordinate_system_index: bool = True, + coordinate_system_index: int = 0, + use_default_material_index: bool = True, + material_index: int = 0, + use_default_node_index: bool = True, + node_index: int = 0, + use_default_element_index: bool = True, + element_index: int = 0, + use_solsh_elements: bool = False, + drop_hanging_nodes: bool = True, + use_solid_model_prefix: bool = True, + _parent_object: ImportedSolidModel | None = None, + _pb_object: Any | None = None, + _attribute_path: str | None = None, + ): + super().__init__( + _parent_object=_parent_object, + _pb_object=_pb_object, + _attribute_path=_attribute_path, + ) + # See comment on DropOffSettings.__init__ for the logic here. + if _parent_object is None and _pb_object is None: + self.use_default_section_index = use_default_section_index + self.section_index = section_index + self.use_default_coordinate_system_index = use_default_coordinate_system_index + self.coordinate_system_index = coordinate_system_index + self.use_default_material_index = use_default_material_index + self.material_index = material_index + self.use_default_node_index = use_default_node_index + self.node_index = node_index + self.use_default_element_index = use_default_element_index + self.element_index = element_index + self.use_solsh_elements = use_solsh_elements + self.drop_hanging_nodes = drop_hanging_nodes + self.use_solid_model_prefix = use_solid_model_prefix + + @classmethod + def _create_default_pb_object(self) -> solid_model_pb2.ExportSettings: + # See comment on DropOffSettings._create_default_pb_object + return solid_model_pb2.ExportSettings() + + use_default_section_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_section_index" + ) + section_index: ReadWriteProperty[int, int] = grpc_data_property("section_index") + use_default_coordinate_system_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_coordinate_system_index" + ) + coordinate_system_index: ReadWriteProperty[int, int] = grpc_data_property( + "coordinate_system_index" + ) + use_default_material_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_material_index" + ) + material_index: ReadWriteProperty[int, int] = grpc_data_property("material_index") + use_default_node_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_node_index" + ) + node_index: ReadWriteProperty[int, int] = grpc_data_property("node_index") + use_default_element_index: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_default_element_index" + ) + element_index: ReadWriteProperty[int, int] = grpc_data_property("element_index") + use_solsh_elements: ReadWriteProperty[bool, bool] = grpc_data_property("use_solsh_elements") + drop_hanging_nodes: ReadWriteProperty[bool, bool] = grpc_data_property("drop_hanging_nodes") + use_solid_model_prefix: ReadWriteProperty[bool, bool] = grpc_data_property( + "use_solid_model_prefix" + ) + + +@mark_grpc_properties +@register +class ImportedSolidModel(SolidModelExportMixin, CreatableTreeObject, IdTreeObject): + """Instantiate an imported solid model. + + Parameters + ---------- + name : + Name of the imported solid model. + active : + Inactive imported solid models are ignored in the analysis. + format : + Specifies the format of the file to be imported. + unit_system : + Specifies the unit system of the external solid mesh. + external_path : + Path of the file to be imported. + delete_bad_elements : + If True, run an element check and delete erroneous elements. Bad elements + can falsify the mapping. + warping_limit : + Maximum allowable warping. Elements with a warping above this limit are + deleted. + Only used if ``delete_bad_elements`` is True. + minimum_volume : + Solid elements with a volume smaller or equal to this value are deleted. + Only used if ``delete_bad_elements`` is True. + cut_off_material : + This material is assigned to the degenerated solid cut-off elements if + ``cut_off_material_handling`` is set to ``GLOBAL`` in the fabric + definition. + export_settings : + Defines the settings for exporting the imported solid model. + + """ + + __slots__: Iterable[str] = tuple() + _COLLECTION_LABEL = "imported_solid_models" + _OBJECT_INFO_TYPE = imported_solid_model_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = imported_solid_model_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "ImportedSolidModel", + active: bool = True, + format: SolidModelImportFormat = "ansys:cdb", # type: ignore + unit_system: UnitSystemType = "from_file", + external_path: _PATH = "", + delete_bad_elements: bool = True, + warping_limit: float = 0.4, + minimum_volume: float = 0.0, + cut_off_material: Material | None = None, + export_settings: ImportedSolidModelExportSettings = ImportedSolidModelExportSettings(), + ): + super().__init__(name=name) + self.active = active + self.format = format + self.unit_system = unit_system + self.external_path = external_path + self.delete_bad_elements = delete_bad_elements + self.warping_limit = warping_limit + self.minimum_volume = minimum_volume + self.cut_off_material = cut_off_material + self.export_settings = export_settings + + def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: + return imported_solid_model_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + locked: ReadOnlyProperty[bool] = grpc_data_property_read_only("properties.locked") + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + format = grpc_data_property( + "properties.format", + from_protobuf=solid_model_import_format_from_pb, + to_protobuf=solid_model_import_format_to_pb, + ) + unit_system = grpc_data_property( + "properties.unit_system", + from_protobuf=unit_system_type_from_pb, + to_protobuf=unit_system_type_to_pb, + ) + external_path: ReadWriteProperty[str, _PATH] = grpc_data_property( + "properties.external_path", to_protobuf=str + ) + delete_bad_elements: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.delete_bad_elements" + ) + warping_limit: ReadWriteProperty[float, float] = grpc_data_property("properties.warping_limit") + minimum_volume: ReadWriteProperty[float, float] = grpc_data_property( + "properties.minimum_volume" + ) + cut_off_material = grpc_link_property("properties.cut_off_material", allowed_types=(Material,)) + export_settings = nested_grpc_object_property( + "properties.export_settings", ImportedSolidModelExportSettings + ) + + elemental_data = elemental_data_property(ImportedSolidModelElementalData) + nodal_data = nodal_data_property(ImportedSolidModelNodalData) + + def refresh(self) -> None: + """Re-import the solid model from the external file.""" + with wrap_grpc_errors(): + self._get_stub().Refresh( # type: ignore + imported_solid_model_pb2.RefreshRequest(resource_path=self._resource_path) + ) + + def import_initial_mesh(self) -> None: + """Import the solid mesh and its element sets.""" + with wrap_grpc_errors(): + self._get_stub().ImportInitialMesh( # type: ignore + imported_solid_model_pb2.ImportInitialMeshRequest(resource_path=self._resource_path) + ) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 14a2bc22db..c194068f33 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -44,6 +44,7 @@ field_definition_pb2_grpc, geometrical_selection_rule_pb2_grpc, imported_modeling_group_pb2_grpc, + imported_solid_model_pb2_grpc, lookup_table_1d_pb2_grpc, lookup_table_3d_pb2_grpc, material_pb2, @@ -118,6 +119,7 @@ from .field_definition import FieldDefinition from .geometrical_selection_rule import GeometricalSelectionRule from .imported_modeling_group import ImportedModelingGroup +from .imported_solid_model import ImportedSolidModel from .lookup_table_1d import LookUpTable1D from .lookup_table_3d import LookUpTable3D from .material import Material @@ -755,6 +757,16 @@ def export_modeling_ply_geometries( ) solid_models = define_mutable_mapping(SolidModel, solid_model_pb2_grpc.ObjectServiceStub) + create_imported_solid_model = define_create_method( + ImportedSolidModel, + func_name="create_imported_solid_model", + parent_class_name="Model", + module_name=__module__, + ) + imported_solid_models = define_mutable_mapping( + ImportedSolidModel, imported_solid_model_pb2_grpc.ObjectServiceStub + ) + create_sensor = define_create_method( Sensor, func_name="create_sensor", parent_class_name="Model", module_name=__module__ ) diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index fd5a652dc5..dceb55e6f0 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -23,21 +23,17 @@ from __future__ import annotations from collections.abc import Iterable -import typing +import dataclasses from typing import Any from ansys.api.acp.v0 import ( extrusion_guide_pb2_grpc, snap_to_geometry_pb2_grpc, - solid_model_export_pb2, solid_model_pb2, solid_model_pb2_grpc, ) -from .._typing_helper import PATH as _PATH -from .._utils.path_to_str import path_to_str_checked from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty -from ._grpc_helpers.exceptions import wrap_grpc_errors from ._grpc_helpers.linked_object_list import ( define_linked_object_list, define_polymorphic_linked_object_list, @@ -49,6 +45,14 @@ grpc_link_property, mark_grpc_properties, ) +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) +from ._solid_model_export import SolidModelExportMixin from .base import ( CreatableTreeObject, IdTreeObject, @@ -61,16 +65,12 @@ DropOffType, ExtrusionMethodType, OffsetDirectionType, - SolidModelExportFormat, - SolidModelSkinExportFormat, drop_off_type_from_pb, drop_off_type_to_pb, extrusion_method_type_from_pb, extrusion_method_type_to_pb, offset_direction_type_from_pb, offset_direction_type_to_pb, - solid_model_export_format_to_pb, - solid_model_skin_export_format_to_pb, status_type_from_pb, ) from .extrusion_guide import ExtrusionGuide @@ -80,7 +80,25 @@ from .oriented_selection_set import OrientedSelectionSet from .snap_to_geometry import SnapToGeometry -__all__ = ["SolidModel", "DropOffSettings", "ExportSettings"] +__all__ = [ + "SolidModel", + "DropOffSettings", + "SolidModelExportSettings", + "SolidModelElementalData", + "SolidModelNodalData", +] + + +@dataclasses.dataclass +class SolidModelElementalData(ElementalData): + """Represents elemental data for a Solid Model.""" + + normal: VectorData | None = None + + +@dataclasses.dataclass +class SolidModelNodalData(NodalData): + """Represents nodal data for a Solid Model.""" @mark_grpc_properties @@ -181,7 +199,7 @@ def _create_default_pb_object(self) -> solid_model_pb2.DropOffSettings: @mark_grpc_properties -class ExportSettings(TreeObjectAttributeWithCache): +class SolidModelExportSettings(TreeObjectAttributeWithCache): """Defines the settings for exporting a solid model. Parameters @@ -323,7 +341,7 @@ def _create_default_pb_object(self) -> solid_model_pb2.ExportSettings: @mark_grpc_properties @register -class SolidModel(CreatableTreeObject, IdTreeObject): +class SolidModel(SolidModelExportMixin, CreatableTreeObject, IdTreeObject): """Instantiate a solid model. Parameters @@ -403,7 +421,7 @@ def __init__( warping_limit: float = 0.4, minimum_volume: float = 0.0, drop_off_settings: DropOffSettings = DropOffSettings(), - export_settings: ExportSettings = ExportSettings(), + export_settings: SolidModelExportSettings = SolidModelExportSettings(), ): super().__init__( name=name, @@ -461,7 +479,9 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: ) drop_off_settings = nested_grpc_object_property("properties.drop_off_settings", DropOffSettings) - export_settings = nested_grpc_object_property("properties.export_settings", ExportSettings) + export_settings = nested_grpc_object_property( + "properties.export_settings", SolidModelExportSettings + ) create_extrusion_guide = define_create_method( ExtrusionGuide, @@ -483,44 +503,5 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: SnapToGeometry, snap_to_geometry_pb2_grpc.ObjectServiceStub ) - def export(self, *, path: _PATH, format: SolidModelExportFormat) -> None: - """Export the solid model to a file. - - Parameters - ---------- - path : - Path to the file where the solid model is saved. - format : - Format of the exported file. Available formats are ``"ansys:h5"`` - and ``"ansys:cdb"``. - - """ - with wrap_grpc_errors(): - self._get_stub().ExportToFile( # type: ignore - solid_model_export_pb2.ExportToFileRequest( - resource_path=self._resource_path, - path=path_to_str_checked(path), - format=typing.cast(typing.Any, solid_model_export_format_to_pb(format)), - ) - ) - - def export_skin(self, *, path: _PATH, format: SolidModelSkinExportFormat) -> None: - """Export the skin of the solid model to a file. - - Parameters - ---------- - path : - Path to the file where the solid model skin is saved. - format : - Format of the exported file. Available formats are ``"ansys:cdb"``, - ``"step"``, ``"iges"``, and ``"stl"``. - - """ - with wrap_grpc_errors(): - self._get_stub().ExportSkin( # type: ignore - solid_model_export_pb2.ExportSkinRequest( - resource_path=self._resource_path, - path=path_to_str_checked(path), - format=typing.cast(typing.Any, solid_model_skin_export_format_to_pb(format)), - ) - ) + elemental_data = elemental_data_property(SolidModelElementalData) + nodal_data = nodal_data_property(SolidModelNodalData) diff --git a/tests/conftest.py b/tests/conftest.py index 3823bdfc11..2bcfad19a3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -306,3 +306,21 @@ def inner(version: str): pytest.skip(f"Test is not supported before version {version}") return inner + + +@pytest.fixture +def tempdir_if_local_acp(acp_instance): + """ + Context manager which provides a temporary directory if the ACP server is local. + Otherwise, an empty path is provided. + """ + + @contextmanager + def inner(): + if acp_instance.is_remote: + yield pathlib.PurePosixPath(".") + else: + with tempfile.TemporaryDirectory() as tmp_dir: + yield pathlib.Path(tmp_dir) + + return inner diff --git a/tests/data/minimal_complete_model_no_matml_link.acph5 b/tests/data/minimal_complete_model_no_matml_link.acph5 index cbe03f3189a67532641601dd59ba6085f0c7a92c..c426389652fa2372b4be4a9d64086fb1e46b93c2 100644 GIT binary patch delta 1553 zcmZvYdpOez7{_;EE^Um>t(Y{LF`=BSbjbZ$9AtrPK98cf>7Nq>Fw)h4UR8xOun-V{Q!K7wf!UD#a0UH0&klVM zFjxF*!8TC{Mv{xz0J(_s42TC}aqEWq+ZqgT@=P$Eg^=sUVX*wXRqP(p9 z1D#rYl2Ci$zW=F5eTEofphS0SYPw*T)?lojVuFC%xX%v7bL~l{JvWXj3^!?10M{2S0GAef|w$1 z!TJ93bo9#vs3r}l>e@4OVnOg4W$;Q!<@X3K8aCZ&BYax9U`<*5(n=UhMxCgK&vgFQ zkqXZZzp3?zkfElK%8;lp3`3IXvp7h%{$#mOo1k_;q6j=`la>NP!1Db?FEm_5kAYq>2+rL`Qb3k})> zGfJ|qpnVo4f-X-LsyM!L)@u_?MDL%E@qADo#R)YFw6eyzXIfD3=elIu5X$eJ($k!o zZZs9SR6l&% zadgejMelf^)686M8^WIstLZB%?;ukX$aChjhX*&+(VMMm*$2EAmZ}n~2LhaN<(}T= znL+u{v%|SG%9e3Xm(R;u8ri|CptVzYN@m*rsk;*RkVCZP`EZjfb`<_4Kb_V)PKUqn zdO)pj5I5pNsWQ7GBxxz*i&5m?{j4%<#u*@^6S)2j{lqJyF7s z9o8%TIf}Q$rc4u12EvQ+=Z3njKY>17g7sR+7qLo)w!JWgoeHwYO0_)Wg}xMk-d^ZeNTx>&C!r_|B}9N#sbwSj94! zB(1yIqs`>X{N7{*=AYv*vt?{YV@EOUJ#rT_T0H2%xVr!R)7L&JLX!Po8 0 + + +@given(invalid_format=st.text()) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +def test_export_with_invalid_format_raises(parent_object, invalid_format): + """Check that the export to a file with an invalid format raises an exception.""" + assume(invalid_format not in ["ansys:h5", "ansys:cdb"]) + + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempfile.TemporaryDirectory() as tmp_dir: + out_file_name = f"out_file.h5" + out_path = pathlib.Path(tmp_dir) / out_file_name + + with pytest.raises(ValueError): + solid_model.export(path=out_path, format=invalid_format) + + +@pytest.mark.parametrize("format", ["ansys:cdb", "step", "iges", "stl"]) +def test_skin_export(acp_instance, parent_object, format): + """Check that the skin export to a file works.""" + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempfile.TemporaryDirectory() as tmp_dir: + ext = {"ansys:cdb": ".cdb", "step": ".stp", "iges": ".igs", "stl": ".stl"}[format] + out_file_name = f"out_file{ext}" + out_path = pathlib.Path(tmp_dir) / out_file_name + + if not acp_instance.is_remote: + # save directly to the local file, to avoid a copy in the working directory + out_file_name = out_path # type: ignore + + solid_model.export_skin(path=out_file_name, format=format) + acp_instance.download_file(out_file_name, out_path) + + assert out_path.exists() + assert out_path.stat().st_size > 0 + + +@given(invalid_format=st.text()) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +def test_skin_export_with_invalid_format_raises(parent_object, invalid_format): + """Check that the export to a file with an invalid format raises an exception.""" + assume(invalid_format not in ["ansys:cdb", "step", "iges", "stl"]) + + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempfile.TemporaryDirectory() as tmp_dir: + out_file_name = f"out_file.stp" + out_path = pathlib.Path(tmp_dir) / out_file_name + + with pytest.raises(ValueError): + solid_model.export_skin(path=out_path, format=invalid_format) + + +def test_refresh(tempdir_if_local_acp, parent_object): + """Check that refreshing works. Does not check the result of the refresh.""" + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempdir_if_local_acp() as tmp_dir: + out_file_name = f"out_file.h5" + out_path = pathlib.Path(tmp_dir) / out_file_name + solid_model.export(path=out_path, format=pyacp.SolidModelExportFormat.ANSYS_H5) + + imported_solid_model = model.create_imported_solid_model( + external_path=out_path, + format=pyacp.SolidModelImportFormat.ANSYS_H5, + ) + imported_solid_model.refresh() + + +@given(external_path=st.text()) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +def test_refresh_inexistent_path(parent_object, external_path): + """Check that refreshing with an inexistent path raises an exception.""" + assume(not pathlib.Path(external_path).exists()) + model = parent_object + imported_solid_model = model.create_imported_solid_model() + imported_solid_model.external_path = external_path + with pytest.raises(RuntimeError): + imported_solid_model.refresh() + + +def test_import_initial_mesh(tempdir_if_local_acp, parent_object): + """Check that the 'import_initial_mesh' method works. Does not check the result.""" + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + with tempdir_if_local_acp() as tmp_dir: + out_file_name = f"out_file.h5" + out_path = pathlib.Path(tmp_dir) / out_file_name + solid_model.export(path=out_path, format=pyacp.SolidModelExportFormat.ANSYS_H5) + + imported_solid_model = model.create_imported_solid_model( + external_path=out_path, + format=pyacp.SolidModelImportFormat.ANSYS_H5, + ) + imported_solid_model.import_initial_mesh() diff --git a/tests/unittests/test_solid_model.py b/tests/unittests/test_solid_model.py index d7111c3bbc..9bdc5e22b2 100644 --- a/tests/unittests/test_solid_model.py +++ b/tests/unittests/test_solid_model.py @@ -123,7 +123,7 @@ def object_properties(parent_object): ( "export_settings", PropertyWithCustomComparison( - initial_value=pyacp.ExportSettings( + initial_value=pyacp.SolidModelExportSettings( use_default_section_index=False, section_index=2, use_default_coordinate_system_index=False, @@ -372,3 +372,31 @@ def test_skin_export_with_invalid_format_raises(parent_object, invalid_format): with pytest.raises(ValueError): solid_model.export_skin(path=out_path, format=invalid_format) + + +def test_elemental_data(parent_object): + """Check that the elemental data can be accessed.""" + model = parent_object + model.fabrics["Fabric.1"].thickness = 0.1 + + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + elemental_data = solid_model.elemental_data + empty_keys = [key for key, value in vars(elemental_data).items() if value is None] + assert not empty_keys, f"Keys with None values: {empty_keys}" + + +def test_nodal_data(parent_object): + """Check that the nodal data can be accessed.""" + model = parent_object + model.fabrics["Fabric.1"].thickness = 0.1 + + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + + nodal_data = solid_model.nodal_data + empty_keys = [key for key, value in vars(nodal_data).items() if value is None] + assert not empty_keys, f"Keys with None values: {empty_keys}" From 7c99a571c531f6a0f1640dbdaf10e9f83d0248a6 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 5 Nov 2024 12:15:37 +0100 Subject: [PATCH 42/96] Allow loading materials from MatML XML (#643) Add exposure of the `ImportMaterialFiles` API method. Adds basic tests for both material export and import. Closes #477 --- src/ansys/acp/core/_tree_objects/model.py | 41 +++++++++++++++++++++++ tests/unittests/test_model.py | 36 ++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index c194068f33..56cebf03c4 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -439,6 +439,47 @@ def export_shell_composite_definitions(self, path: _PATH) -> None: ) ) + @supported_since("25.1") + def import_materials( + self, + matml_path: _PATH, + *, + material_apdl_path: _PATH | None = None, + ) -> None: + """ + Import materials from a MatML file. + + Import materials from a ``MatML.xml`` (Engineering Data) file. + + Optionally, a material APDL file can be defined. This is a pre-generated + solver snippet, needed in case of variable materials or non-standard + material models. The snippet is used when exporting solid models or + surface section cuts in the CDB format. + + Parameters + ---------- + matml_path: + File path to the MatML file. + material_apdl_path: + File path to the material APDL file. + """ + material_stub = material_pb2_grpc.ObjectServiceStub(self._channel) + collection_path = CollectionPath( + value=rp_join(self._resource_path.value, Material._COLLECTION_LABEL) + ) + with wrap_grpc_errors(): + material_stub.ImportMaterialFiles( + material_pb2.ImportMaterialFilesRequest( + collection_path=collection_path, + matml_path=path_to_str_checked(matml_path), + material_apdl_path=( + path_to_str_checked(material_apdl_path) + if material_apdl_path is not None + else "" + ), + ) + ) + def export_materials(self, path: _PATH) -> None: """ Write materials to a XML (MatML) file. diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index dbb469ba9c..29e2ca27bc 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -314,3 +314,39 @@ def test_change_unit_system(minimal_complete_model, unit_system, raises_before_v minimal_complete_model.minimum_analysis_ply_thickness, initial_minimum_analysis_ply_thickness * conversion_factor_by_us[unit_system.value], ) + + +def test_material_export(acp_instance, minimal_complete_model, tempdir_if_local_acp): + """Check that the 'export_materials' method produces a file.""" + with tempdir_if_local_acp() as export_dir: + with tempfile.TemporaryDirectory() as tmp_dir: + export_file_path = pathlib.Path(export_dir) / "material_exported.xml" + local_file_path = pathlib.Path(tmp_dir) / "material_exported.xml" + + minimal_complete_model.export_materials(export_file_path) + acp_instance.download_file(export_file_path, local_file_path) + + assert local_file_path.exists() + assert os.stat(local_file_path).st_size > 0 + + +def test_material_import(minimal_complete_model, tempdir_if_local_acp, raises_before_version): + # GIVEN: a model, and a material XML file containing a material which is + # not present in the model + + with tempdir_if_local_acp() as export_dir: + new_mat_id = "New Material" + export_file_path = pathlib.Path(export_dir) / "material_exported.xml" + mat = minimal_complete_model.materials["Structural Steel"].clone() + mat.name = new_mat_id + mat.store(parent=minimal_complete_model) + minimal_complete_model.export_materials(export_file_path) + del minimal_complete_model.materials[new_mat_id] + assert new_mat_id not in minimal_complete_model.materials + + with raises_before_version("25.1"): + # WHEN: the materials are imported + minimal_complete_model.import_materials(export_file_path) + + # THEN: the new material should be present in the model + assert new_mat_id in minimal_complete_model.materials From 46d8d0e9c7b119fc332e57191dae5dc763cb534c Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 10:40:56 +0100 Subject: [PATCH 43/96] Doc: convert StrEnum defaults to their string value (#651) When building the documentation, convert any parameter defaults which are of type StrEnum to their string value, so that the string is shown in the signature rather than the enum name. Closes #648 --- doc/source/conf.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index c2874f921d..50fa46b0c8 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -55,6 +55,7 @@ def _signature( ) from ansys.acp.core._tree_objects.sensor import _LINKABLE_ENTITY_TYPES # noqa: F401 from ansys.acp.core._tree_objects.sublaminate import _LINKABLE_MATERIAL_TYPES # noqa: F401 + from ansys.acp.core._typing_helper import StrEnum from ansys.dpf.composites.data_sources import ContinuousFiberCompositesFiles # noqa: F401 from ansys.dpf.core import UnitSystem # noqa: F401 @@ -78,7 +79,13 @@ def _signature( "Sequence[_LINKABLE_ENTITY_TYPES]", "_LINKABLE_MATERIAL_TYPES", ]: - parameters[i] = param.replace(annotation=eval(param.annotation)) + param = param.replace(annotation=eval(param.annotation)) + # Represent StrEnum defaults as their string value, since this is + # easier to understand for library users. The enum type is still + # available in the type annotations. + if isinstance(param.default, StrEnum): + param = param.replace(default=param.default.value) + parameters[i] = param signature = signature.replace(parameters=parameters) return signature From ba3976e9c9db2b06d043813882c8316ceba949c1 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 13:11:29 +0100 Subject: [PATCH 44/96] Add vulnerability check CI job (#650) Add usage of the `check-vulnerabilities` action, to check for code vulnerabilities. Checks for the use of `assert` are globally disabled. The reason for this check is that `assert` can be misused to check for invalid input, but we use it to validate programmer assumptions (i.e., they should never raise unless there is a bug). For the identified issues which were ignored (related to launching the ACP gRPC server executable), a "security considerations" page is added to the documentation. Since we are not certain if the `check-vulnerabilities` action works on `main` with current permissions, CI is configured to continue when it fails. --- .github/workflows/ci_cd.yml | 13 +++- .gitignore | 3 + doc/source/user_guide/index.rst | 1 + .../user_guide/security_considerations.rst | 67 +++++++++++++++++++ .../config/vocabularies/ANSYS/accept.txt | 1 + src/.bandit | 8 +++ src/ansys/acp/core/_server/acp_instance.py | 12 ++++ src/ansys/acp/core/_server/direct.py | 4 +- src/ansys/acp/core/_server/docker_compose.py | 22 ++++-- src/ansys/acp/core/_server/launch.py | 5 ++ src/ansys/acp/core/example_helpers.py | 3 +- 11 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 doc/source/user_guide/security_considerations.rst create mode 100644 src/.bandit diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 4971951dc8..cd07504d2d 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -106,6 +106,17 @@ jobs: operating-system: ${{ matrix.os }} python-version: ${{ matrix.python-version }} + check-vulnerabilities: + name: "Check library vulnerabilities" + runs-on: ubuntu-latest + steps: + - uses: ansys/actions/check-vulnerabilities@v8.1 + with: + python-version: ${{ env.MAIN_PYTHON_VERSION }} + token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} + python-package-name: 'ansys-acp-core' + dev-mode: ${{ github.ref != 'refs/heads/main' }} + testing: name: Testing runs-on: ubuntu-latest @@ -418,7 +429,7 @@ jobs: build: name: Build library runs-on: ubuntu-latest - needs: [code-style, testing, doc-style, docs, build-wheelhouse, doctest] + needs: [code-style, testing, doc-style, docs, build-wheelhouse, doctest] # TODO: add check-vulnerabilities once we know it works on main timeout-minutes: 30 steps: - name: Build library source and wheel artifacts diff --git a/.gitignore b/.gitignore index 02b322703b..26bdfceb32 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,6 @@ examples/pymechanical/output/* /examples/workbench_project/ +# Vulnerability scanning +info_bandit.json +info_safety.json diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index d710537dc0..be8fec05bd 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -11,3 +11,4 @@ section provides in-depth explanations. howto/index concepts/index + security_considerations diff --git a/doc/source/user_guide/security_considerations.rst b/doc/source/user_guide/security_considerations.rst new file mode 100644 index 0000000000..366c7c327e --- /dev/null +++ b/doc/source/user_guide/security_considerations.rst @@ -0,0 +1,67 @@ +Security considerations +======================= + +This section provides information on security considerations for the use +of PyACP. It is important to understand the capabilities which PyACP +provides, especially when using it to build apps or scripts that accept +untrusted input. + +.. _security_launch_acp: + +Launching ACP +------------- + +The :py:func:`.launch_acp` function has different security implications depending +on the launch mode used: + +Direct launch +^^^^^^^^^^^^^ + +When using the ``"direct"`` launch mode: + +- The executable which is launched is configurable either in the function + parameters, or in the ``ansys-tools-local-product-launcher`` configuration + file. This may allow an attacker to launch arbitrary executables on the system. +- The standard output and standard error file paths are configurable. This may + be used to overwrite arbitrary files on the system. + +When exposing the ``"direct"`` launch mode to untrusted users, it is important +to validate that the executable path and file paths are safe, or hard-code +them in the app. + +Docker compose launch +^^^^^^^^^^^^^^^^^^^^^ + +The ``"docker_compose"`` launch mode executes the ``docker`` or ``docker-compose`` +commands on the system. + +This may pose the following risks: + +- If the user can override which container is launched, they may be able to + launch arbitrary containers on the system. This is especially problematic + if ``docker`` is configured to run with elevated privileges. +- If the user can override the ``docker`` or ``docker-compose`` executable + in the environment, they may be able to execute arbitrary commands on the + system. + +When exposing the ``"docker_compose"`` launch mode to untrusted users, it is important +to validate that the container being launched, and control the environment the +command is executed in. + +Connect launch +^^^^^^^^^^^^^^ + +The ``"connect"`` launch mode connects to an existing ACP server. This mode does +not pose any particular security risks, besides allowing access to a port on the +system. + +.. _security_file_upload_download: + +File up- and downloads +---------------------- + +The :py:meth:`.ACP.upload_file` and :py:meth:`.ACP.download_file` methods create files +on the local or remote machine, without any validation of the file content or path. + +When exposing these methods to untrusted users, it is important to validate that +only files that are safe to be uploaded or downloaded are processed. diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index 019c433cb3..0563510018 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -8,3 +8,4 @@ GUI Mechanical APDL API +untrusted diff --git a/src/.bandit b/src/.bandit new file mode 100644 index 0000000000..59de086a34 --- /dev/null +++ b/src/.bandit @@ -0,0 +1,8 @@ +# TODO: move this configuration into the main 'pyproject.toml' file +# once this is supported by the check-vulnerabilities action. + +# Skipping B101:assert_used. Assert statements should not be used to +# check for 'regular' error conditions, but rather for detecting internal +# errors that should never occur. +[bandit] +skips = B101 diff --git a/src/ansys/acp/core/_server/acp_instance.py b/src/ansys/acp/core/_server/acp_instance.py index 868705d052..994d78dfea 100644 --- a/src/ansys/acp/core/_server/acp_instance.py +++ b/src/ansys/acp/core/_server/acp_instance.py @@ -227,6 +227,12 @@ def models(self) -> tuple[Model, ...]: def upload_file(self, local_path: _PATH) -> pathlib.PurePath: """Upload a file to the server. + .. warning:: + + Do not execute this function with untrusted input parameters. + See the :ref:`security guide` + for details. + Parameters ---------- local_path : @@ -242,6 +248,12 @@ def upload_file(self, local_path: _PATH) -> pathlib.PurePath: def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: """Download a file from the server. + .. warning:: + + Do not execute this function with untrusted input parameters. + See the :ref:`security guide` + for details. + Parameters ---------- remote_filename : diff --git a/src/ansys/acp/core/_server/direct.py b/src/ansys/acp/core/_server/direct.py index 6d1bdde5a9..e96c851fdb 100644 --- a/src/ansys/acp/core/_server/direct.py +++ b/src/ansys/acp/core/_server/direct.py @@ -23,7 +23,7 @@ import dataclasses import os import pathlib -import subprocess +import subprocess # nosec B404 from typing import TextIO import grpc @@ -112,7 +112,7 @@ def start(self) -> None: self._url = f"localhost:{port}" self._stdout = open(stdout_file, mode="w", encoding="utf-8") self._stderr = open(stderr_file, mode="w", encoding="utf-8") - self._process = subprocess.Popen( + self._process = subprocess.Popen( # nosec B603: documented in 'security_considerations.rst' [ self._config.binary_path, f"--server-address=0.0.0.0:{port}", diff --git a/src/ansys/acp/core/_server/docker_compose.py b/src/ansys/acp/core/_server/docker_compose.py index 091825f441..e531e1914a 100644 --- a/src/ansys/acp/core/_server/docker_compose.py +++ b/src/ansys/acp/core/_server/docker_compose.py @@ -29,7 +29,7 @@ import math import os import pathlib -import subprocess +import subprocess # nosec B404 import uuid import grpc @@ -139,17 +139,21 @@ def __init__(self, *, config: DockerComposeLaunchConfig): try: self._compose_version = parse_version( - subprocess.check_output( + subprocess.check_output( # nosec B603, B607: documented in 'security_considerations.rst' ["docker", "compose", "version", "--short"], text=True - ).replace("-", "+") + ).replace( + "-", "+" + ) ) self._compose_cmds = ["docker", "compose"] except subprocess.CalledProcessError: # If 'docker compose does not work, try 'docker-compose' instead. self._compose_version = parse_version( - subprocess.check_output( + subprocess.check_output( # nosec B603, B607: documented in 'security_considerations.rst' ["docker-compose", "version", "--short"], text=True - ).replace("-", "+") + ).replace( + "-", "+" + ) ) self._compose_cmds = ["docker-compose"] @@ -188,7 +192,7 @@ def start(self) -> None: # The '--wait' flag is only available from version >= 2.1.1 of docker compose: # https://github.com/docker/compose/commit/72e4519cbfb6cdfc600e6ebfa377ce4b8e162c78 cmd.append("--wait") - subprocess.check_call( + subprocess.check_call( # nosec B603: documented in 'security_considerations.rst' cmd, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) @@ -208,7 +212,11 @@ def stop(self, *, timeout: float | None = None) -> None: cmd.extend(["--timeout", str(math.ceil(timeout))]) if not self._keep_volume: cmd.append("--volumes") - subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.check_call( # nosec B603: documented in 'security_considerations.rst' + cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) def check(self, timeout: float | None = None) -> bool: for url in self.urls.values(): diff --git a/src/ansys/acp/core/_server/launch.py b/src/ansys/acp/core/_server/launch.py index 1609179db0..741276d388 100644 --- a/src/ansys/acp/core/_server/launch.py +++ b/src/ansys/acp/core/_server/launch.py @@ -51,6 +51,11 @@ def launch_acp( Launch the ACP gRPC server with the given configuration. If no configuration is provided, the configured default is used. + .. warning:: + + Do not execute this function with untrusted input parameters. + See the :ref:`security guide` for details. + Parameters ---------- config : diff --git a/src/ansys/acp/core/example_helpers.py b/src/ansys/acp/core/example_helpers.py index b42b70bc97..1ab881438c 100644 --- a/src/ansys/acp/core/example_helpers.py +++ b/src/ansys/acp/core/example_helpers.py @@ -113,7 +113,8 @@ def _get_file_url(example_location: _ExampleLocation) -> str: def _download_file(example_location: _ExampleLocation, local_path: pathlib.Path) -> None: file_url = _get_file_url(example_location) - urllib.request.urlretrieve(file_url, local_path) + # The URL is hard-coded to start with the example repository URL, so it is safe to use + urllib.request.urlretrieve(file_url, local_path) # nosec: B310 def _run_analysis(workflow: "ACPWorkflow") -> None: From ce099e6195a6f2b437ff6af52a080cc8895ecad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Wed, 6 Nov 2024 13:58:36 +0100 Subject: [PATCH 45/96] Feat/596 add solid element set (#655) * add solid element set --------- Co-authored-by: Dominik Gresch --- doc/source/api/mesh_data.rst | 2 + doc/source/api/tree_objects.rst | 1 + doc/source/index.rst | 4 +- src/ansys/acp/core/__init__.py | 6 ++ src/ansys/acp/core/_tree_objects/__init__.py | 8 ++ .../_tree_objects/_grpc_helpers/mapping.py | 8 +- .../_tree_objects/imported_solid_model.py | 7 ++ .../core/_tree_objects/solid_element_set.py | 78 ++++++++++++++++ .../acp/core/_tree_objects/solid_model.py | 12 ++- tests/unittests/test_solid_element_set.py | 91 +++++++++++++++++++ 10 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/solid_element_set.py create mode 100644 tests/unittests/test_solid_element_set.py diff --git a/doc/source/api/mesh_data.rst b/doc/source/api/mesh_data.rst index e225b06cf6..ad79479215 100644 --- a/doc/source/api/mesh_data.rst +++ b/doc/source/api/mesh_data.rst @@ -32,6 +32,8 @@ Mesh data objects ProductionPlyElementalData ProductionPlyNodalData ScalarData + SolidElementSetElementalData + SolidElementSetNodalData SolidModelElementalData SolidModelNodalData SphericalSelectionRuleElementalData diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 6e119f3154..a59dff2fca 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -43,6 +43,7 @@ ACP objects SectionCut Sensor SnapToGeometry + SolidElementSet SolidModel SolidModelExportSettings SphericalSelectionRule diff --git a/doc/source/index.rst b/doc/source/index.rst index b4664d478b..144a38125c 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -76,7 +76,9 @@ optimization of composite structures. Limitations ^^^^^^^^^^^ -* FieldDefinitions for variable material properties are not supported +* Field definitions are currently only supported through (Py)Mechanical. + The workflow from PyACP to (Py)MADL ignores field definitions. +* The PyACP to PyMADL workflow does not fully support variable materials * Visualization and mesh data of imported plies are not supported yet * Section cuts cannot be visualized * Sampling point analysis data is not available diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index b58b593551..10552672dd 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -142,6 +142,9 @@ Sensor, SensorType, SnapToGeometry, + SolidElementSet, + SolidElementSetElementalData, + SolidElementSetNodalData, SolidModel, SolidModelElementalData, SolidModelExportFormat, @@ -297,6 +300,9 @@ "Sensor", "SensorType", "SnapToGeometry", + "SolidElementSet", + "SolidElementSetNodalData", + "SolidElementSetElementalData", "SolidModel", "SolidModelElementalData", "SolidModelExportFormat", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 5e8f17df82..6cc1717603 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -130,6 +130,11 @@ from .section_cut import SectionCut from .sensor import Sensor from .snap_to_geometry import SnapToGeometry +from .solid_element_set import ( + SolidElementSet, + SolidElementSetElementalData, + SolidElementSetNodalData, +) from .solid_model import ( DropOffSettings, SolidModel, @@ -262,6 +267,9 @@ "Sensor", "SensorType", "SnapToGeometry", + "SolidElementSet", + "SolidElementSetElementalData", + "SolidElementSetNodalData", "SolidModel", "SolidModelElementalData", "SolidModelExportFormat", diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py index 3254215084..57c571e154 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py @@ -44,7 +44,13 @@ ValueT = TypeVar("ValueT", bound=TreeObjectBase) CreatableValueT = TypeVar("CreatableValueT", bound=CreatableTreeObject) -__all__ = ["Mapping", "MutableMapping", "define_mutable_mapping", "define_create_method"] +__all__ = [ + "Mapping", + "MutableMapping", + "define_mutable_mapping", + "define_create_method", + "get_read_only_collection_property", +] class Mapping(ObjectCacheMixin, Generic[ValueT]): diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index ed4e8b1baf..cc9b952928 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -30,6 +30,7 @@ enum_types_pb2, imported_solid_model_pb2, imported_solid_model_pb2_grpc, + solid_element_set_pb2_grpc, solid_model_pb2, ) @@ -37,6 +38,7 @@ from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty from ._grpc_helpers.enum_wrapper import wrap_to_string_enum from ._grpc_helpers.exceptions import wrap_grpc_errors +from ._grpc_helpers.mapping import get_read_only_collection_property from ._grpc_helpers.property_helper import ( grpc_data_property, grpc_data_property_read_only, @@ -59,6 +61,7 @@ ) from .material import Material from .object_registry import register +from .solid_element_set import SolidElementSet __all__ = [ "ImportedSolidModel", @@ -310,6 +313,10 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: "properties.export_settings", ImportedSolidModelExportSettings ) + solid_element_sets = get_read_only_collection_property( + SolidElementSet, solid_element_set_pb2_grpc.ObjectServiceStub + ) + elemental_data = elemental_data_property(ImportedSolidModelElementalData) nodal_data = nodal_data_property(ImportedSolidModelNodalData) diff --git a/src/ansys/acp/core/_tree_objects/solid_element_set.py b/src/ansys/acp/core/_tree_objects/solid_element_set.py new file mode 100644 index 0000000000..64186c5eff --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/solid_element_set.py @@ -0,0 +1,78 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable +import dataclasses + +from ansys.api.acp.v0 import solid_element_set_pb2, solid_element_set_pb2_grpc + +from .._utils.array_conversions import to_tuple_from_1D_array +from .._utils.property_protocols import ReadOnlyProperty +from ._grpc_helpers.property_helper import grpc_data_property_read_only, mark_grpc_properties +from ._mesh_data import ElementalData, NodalData +from .base import IdTreeObject, ReadOnlyTreeObject +from .enums import status_type_from_pb +from .object_registry import register + +__all__ = ["SolidElementSet", "SolidElementSetElementalData", "SolidElementSetNodalData"] + + +@dataclasses.dataclass +class SolidElementSetElementalData(ElementalData): + """Represents elemental data for a Solid Element Set.""" + + +@dataclasses.dataclass +class SolidElementSetNodalData(NodalData): + """Represents nodal data for a Solid Element Set.""" + + +@mark_grpc_properties +@register +class SolidElementSet(ReadOnlyTreeObject, IdTreeObject): + """Instantiate a Solid Element Set. + + Parameters + ---------- + name: str + The name of the production ply. + element_labels : + Label of elements. + + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "solid_element_sets" + _OBJECT_INFO_TYPE = solid_element_set_pb2.ObjectInfo + _SUPPORTED_SINCE = "25.1" + + def _create_stub(self) -> solid_element_set_pb2_grpc.ObjectServiceStub: + return solid_element_set_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + locked: ReadOnlyProperty[bool] = grpc_data_property_read_only("properties.locked") + element_labels: ReadOnlyProperty[tuple[int, ...]] = grpc_data_property_read_only( + "properties.element_labels", from_protobuf=to_tuple_from_1D_array + ) diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index dceb55e6f0..5a69149ada 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -29,6 +29,7 @@ from ansys.api.acp.v0 import ( extrusion_guide_pb2_grpc, snap_to_geometry_pb2_grpc, + solid_element_set_pb2_grpc, solid_model_pb2, solid_model_pb2_grpc, ) @@ -38,7 +39,11 @@ define_linked_object_list, define_polymorphic_linked_object_list, ) -from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping +from ._grpc_helpers.mapping import ( + define_create_method, + define_mutable_mapping, + get_read_only_collection_property, +) from ._grpc_helpers.property_helper import ( grpc_data_property, grpc_data_property_read_only, @@ -79,6 +84,7 @@ from .object_registry import register from .oriented_selection_set import OrientedSelectionSet from .snap_to_geometry import SnapToGeometry +from .solid_element_set import SolidElementSet __all__ = [ "SolidModel", @@ -503,5 +509,9 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: SnapToGeometry, snap_to_geometry_pb2_grpc.ObjectServiceStub ) + solid_element_sets = get_read_only_collection_property( + SolidElementSet, solid_element_set_pb2_grpc.ObjectServiceStub + ) + elemental_data = elemental_data_property(SolidModelElementalData) nodal_data = nodal_data_property(SolidModelNodalData) diff --git a/tests/unittests/test_solid_element_set.py b/tests/unittests/test_solid_element_set.py new file mode 100644 index 0000000000..82e4556d30 --- /dev/null +++ b/tests/unittests/test_solid_element_set.py @@ -0,0 +1,91 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import Model, SolidElementSet + +from .common.tree_object_tester import TreeObjectTesterReadOnly + +DUMMY_SM_NAME = "dummy" +ESET_ALL_ELEMENTS = "All_Elements" + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(SolidElementSet._SUPPORTED_SINCE): + pytest.skip("SolidElementSet is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +def add_solid_model_to_model(model: Model): + solid_model = model.create_solid_model( + name=DUMMY_SM_NAME, + element_sets=[model.element_sets[ESET_ALL_ELEMENTS]], + ) + return solid_model + + +class TestSolidElementSet(TreeObjectTesterReadOnly): + COLLECTION_NAME = "solid_element_sets" + + @staticmethod + @pytest.fixture + def parent_object(model: Model): + solid_model = add_solid_model_to_model(model) + # Solid model must be up-to-date to access the solid element sets + model.update() + return solid_model + + @pytest.fixture + def collection_test_data(self, parent_object): + solid_model = parent_object + object_collection = getattr(solid_model, self.COLLECTION_NAME) + object_collection.values() + object_names = [ESET_ALL_ELEMENTS] + object_ids = [ESET_ALL_ELEMENTS] + + return object_collection, object_names, object_ids + + @staticmethod + @pytest.fixture + def properties(): + return { + ESET_ALL_ELEMENTS: { + "status": "UPTODATE", + "locked": True, + "element_labels": (2,), + }, + } + + def test_properties(self, parent_object, properties): + + for solid_element_set in parent_object.solid_element_sets.values(): + ref_values = properties[solid_element_set.id] + for prop, value in ref_values.items(): + assert getattr(solid_element_set, prop) == value From 3da9f6ca1718f3096811aab740b1761855d7b58c Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 14:34:52 +0100 Subject: [PATCH 46/96] Convert path to positional argument (#654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the `path` from a keword-only argument to a positional argument in the import and export methods. This is motivated mostly by the `import_model` method, where the path is the only required argument. As such, it's intuitive to call the function with the path as positional argument. The other export methods (solid model export and solid model skin export) are adapted for consistency. For the model export methods, the `path` was already a positional argument, so no changes were needed. Co-authored-by: René Roos <105842014+roosre@users.noreply.github.com> --- src/ansys/acp/core/_server/acp_instance.py | 2 +- src/ansys/acp/core/_tree_objects/_solid_model_export.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ansys/acp/core/_server/acp_instance.py b/src/ansys/acp/core/_server/acp_instance.py index 994d78dfea..e1c1ef23f4 100644 --- a/src/ansys/acp/core/_server/acp_instance.py +++ b/src/ansys/acp/core/_server/acp_instance.py @@ -132,9 +132,9 @@ def server_version(self) -> str: def import_model( self, + path: _PATH, *, name: str | None = None, - path: _PATH, format: str = "acp:h5", # pylint: disable=redefined-builtin **kwargs: Any, ) -> Model: diff --git a/src/ansys/acp/core/_tree_objects/_solid_model_export.py b/src/ansys/acp/core/_tree_objects/_solid_model_export.py index 659e999c85..dfca22caf3 100644 --- a/src/ansys/acp/core/_tree_objects/_solid_model_export.py +++ b/src/ansys/acp/core/_tree_objects/_solid_model_export.py @@ -43,7 +43,7 @@ class SolidModelExportMixin(CreatableTreeObject): """Mixin class for adding export functionality to the solid model and imported solid model classes.""" - def export(self, *, path: _PATH, format: SolidModelExportFormat) -> None: + def export(self, path: _PATH, *, format: SolidModelExportFormat) -> None: """Export the solid model to a file. Parameters @@ -64,7 +64,7 @@ def export(self, *, path: _PATH, format: SolidModelExportFormat) -> None: ) ) - def export_skin(self, *, path: _PATH, format: SolidModelSkinExportFormat) -> None: + def export_skin(self, path: _PATH, *, format: SolidModelSkinExportFormat) -> None: """Export the skin of the solid model to a file. Parameters From 702df6cd2036257f5b9034cc72a0a064923494a8 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 15:08:10 +0100 Subject: [PATCH 47/96] Show the prev / next button (#653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show the prev / next buttons in the documentation. Closes #540. Co-authored-by: René Roos <105842014+roosre@users.noreply.github.com> --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 50fa46b0c8..b63d08e37c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -156,7 +156,7 @@ def _signature( html_theme_options = { "logo": "pyansys", "github_url": "https://github.com/ansys/pyacp", - "show_prev_next": False, + "show_prev_next": True, "show_breadcrumbs": True, "additional_breadcrumbs": [("PyAnsys", "https://docs.pyansys.com/")], "switcher": { From f2ba4dc970dde44bfe321a434361bd0dabb96f1c Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 15:27:11 +0100 Subject: [PATCH 48/96] Improve errors on wrong type in LinkedObjectList (#656) Add explicit checks for the type of the objects being added to a LinkedObjectList, and raise a TypeError if the type is incorrect. Previously the backend error message was raised as RuntimeError, which is difficult to understand for the user. --- .../_grpc_helpers/linked_object_list.py | 21 +++ tests/unittests/test_linked_object_list.py | 134 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 tests/unittests/test_linked_object_list.py diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py index 2fdbff54e5..eabe1e5f4e 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py @@ -70,11 +70,13 @@ def _initialize_with_cache( parent_object: TreeObject, attribute_name: str, object_constructor: Callable[[ResourcePath, Channel], ValueT], + allowed_types: tuple[type[ValueT], ...], ) -> Self: return cls( _parent_object=parent_object, _attribute_name=attribute_name, _object_constructor=object_constructor, + _allowed_types=allowed_types, ) @staticmethod @@ -95,6 +97,7 @@ def __init__( _parent_object: TreeObject, _attribute_name: str, _object_constructor: Callable[[ResourcePath, Channel], ValueT], + _allowed_types: tuple[Any, ...], ) -> None: getter = grpc_data_getter(_attribute_name, from_protobuf=list) setter = grpc_data_setter(_attribute_name, to_protobuf=lambda x: x) @@ -113,6 +116,8 @@ def set_resourcepath_list(value: list[ResourcePath]) -> None: self._object_constructor: Callable[[ResourcePath], ValueT] = ( lambda resource_path: _object_constructor(resource_path, _parent_object._server_wrapper) ) + self._allowed_types = _allowed_types + self._allowed_types_str = ", ".join([cls.__name__ for cls in _allowed_types]) def __len__(self) -> int: return len(self._get_resourcepath_list()) @@ -139,12 +144,15 @@ def __setitem__(self, key: slice, value: Iterable[ValueT]) -> None: ... def __setitem__(self, key: int | slice, value: ValueT | Iterable[ValueT]) -> None: resource_path_list = self._get_resourcepath_list() if isinstance(value, TreeObject): + self._check_type(value) if not isinstance(key, int): raise TypeError("Cannot assign to a slice with a single object.") resource_path_list[key] = value._resource_path else: if not isinstance(key, slice): raise TypeError("Cannot assign to a single index with an iterable.") + for item in value: + self._check_type(item) resource_path_list[key] = [item._resource_path for item in value] self._set_resourcepath_list(resource_path_list) @@ -174,6 +182,7 @@ def append(self, object: ValueT) -> None: object: Object to append. """ + self._check_type(object) resource_path_list = self._get_resourcepath_list() resource_path_list.append(object._resource_path) self._set_resourcepath_list(resource_path_list) @@ -207,6 +216,8 @@ def extend(self, iterable: Iterable[ValueT]) -> None: Iterable of objects to append. """ resource_path_list = self._get_resourcepath_list() + for it in iterable: + self._check_type(it) resource_path_list.extend([it._resource_path for it in iterable]) self._set_resourcepath_list(resource_path_list) @@ -220,6 +231,7 @@ def insert(self, index: int, object: ValueT) -> None: object: Object to insert. """ + self._check_type(object) resource_path_list = self._get_resourcepath_list() resource_path_list.insert(index, object._resource_path) self._set_resourcepath_list(resource_path_list) @@ -279,6 +291,12 @@ def __eq__(self, other: Any) -> Any: def __repr__(self) -> str: return f"" + def _check_type(self, object: Any) -> None: + if not isinstance(object, self._allowed_types): + raise TypeError( + f"List items must be of type {self._allowed_types_str}, not {type(object).__name__}." + ) + ChildT = TypeVar("ChildT", bound=CreatableTreeObject) @@ -293,6 +311,7 @@ def getter(self: ValueT) -> LinkedObjectList[ChildT]: parent_object=self, attribute_name=attribute_name, object_constructor=object_class._from_resource_path, + allowed_types=(object_class,), ) def setter(self: ValueT, value: list[ChildT]) -> None: @@ -315,12 +334,14 @@ def getter(self: ValueT) -> LinkedObjectList[Any]: if allowed_types_getter is not None: allowed_types = allowed_types_getter() + assert allowed_types is not None return LinkedObjectList( _parent_object=self, _attribute_name=attribute_name, _object_constructor=partial( tree_object_from_resource_path, allowed_types=allowed_types ), + _allowed_types=allowed_types, ) def setter(self: ValueT, value: list[Any]) -> None: diff --git a/tests/unittests/test_linked_object_list.py b/tests/unittests/test_linked_object_list.py new file mode 100644 index 0000000000..42e1bec2c2 --- /dev/null +++ b/tests/unittests/test_linked_object_list.py @@ -0,0 +1,134 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import pytest + + +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def oriented_selection_set(model): + return list(model.oriented_selection_sets.values())[0] + + +def test_error_on_wrong_type(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test replaces the entire contents of a LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.rosettes = [model.create_element_set()] + assert "Rosette" in str(excinfo.value) + + +def test_error_on_wrong_type_polymorphic(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test replaces the entire contents of a polymorphic LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.selection_rules = [model.create_element_set()] + assert "SelectionRule" in str(excinfo.value) + + +def test_error_on_wrong_single_assignment(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test assigns to a single item in a LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.rosettes[0] = model.create_element_set() + assert "Rosette" in str(excinfo.value) + + +def test_error_on_wrong_single_assignment_polymorphic(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test assigns to a single item in a polymorphic LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.selection_rules[0] = model.create_element_set() + assert "SelectionRule" in str(excinfo.value) + + +def test_error_on_wrong_append(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test appends to a LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.rosettes.append(model.create_element_set()) + assert "Rosette" in str(excinfo.value) + + +def test_error_on_wrong_append_polymorphic(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test appends to a polymorphic LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.selection_rules.append(model.create_element_set()) + assert "SelectionRule" in str(excinfo.value) + + +def test_error_on_wrong_insert(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test inserts into a LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.rosettes.insert(0, model.create_element_set()) + assert "Rosette" in str(excinfo.value) + + +def test_error_on_wrong_insert_polymorphic(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test inserts into a polymorphic LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.selection_rules.insert(0, model.create_element_set()) + assert "SelectionRule" in str(excinfo.value) + + +def test_error_on_wrong_extend(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test extends a LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.rosettes.extend([model.create_element_set()]) + assert "Rosette" in str(excinfo.value) + + +def test_error_on_wrong_extend_polymorphic(model, oriented_selection_set): + """Test that the correct error is raised when assigning the wrong type. + + This test extends a polymorphic LinkedObjectList. + """ + with pytest.raises(TypeError) as excinfo: + oriented_selection_set.selection_rules.extend([model.create_element_set()]) + assert "SelectionRule" in str(excinfo.value) From 273ad2075c9a9478150be0641fb27c29c924cffb Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 17:00:01 +0100 Subject: [PATCH 49/96] Add CutOffGeometry exposure (#647) Add cut-off geometry exposure. Rename `OrientationType` to `SnapToGeometryOrientationType`, to distinguish from the new `CutOffGeometryOrientationType`. --- doc/source/api/enum_types.rst | 3 +- doc/source/api/tree_objects.rst | 1 + src/ansys/acp/core/__init__.py | 10 +- src/ansys/acp/core/_tree_objects/__init__.py | 8 +- .../core/_tree_objects/cut_off_geometry.py | 106 ++++++++++++++++++ src/ansys/acp/core/_tree_objects/enums.py | 34 ++++-- .../_tree_objects/imported_solid_model.py | 18 ++- .../core/_tree_objects/snap_to_geometry.py | 12 +- .../acp/core/_tree_objects/solid_model.py | 12 ++ tests/unittests/test_cut_off_geometry.py | 85 ++++++++++++++ tests/unittests/test_snap_to_geometry.py | 6 +- 11 files changed, 270 insertions(+), 25 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/cut_off_geometry.py create mode 100644 tests/unittests/test_cut_off_geometry.py diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 906f313a49..1c87ce4e9f 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -9,6 +9,7 @@ Enumeration data types ArrowType BooleanOperationType + CutOffGeometryOrientationType CutoffMaterialType CutoffRuleType DimensionType @@ -35,7 +36,7 @@ Enumeration data types NodalDataType OffsetDirectionType OffsetType - OrientationType + SnapToGeometryOrientationType PlyCutoffType PlyGeometryExportFormat PlyType diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index a59dff2fca..83462e73e2 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -11,6 +11,7 @@ ACP objects ButtJointSequence CADComponent CADGeometry + CutOffGeometry CutoffSelectionRule CylindricalSelectionRule DropOffSettings diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 10552672dd..978c54fa3b 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -51,6 +51,8 @@ ButtJointSequence, CADComponent, CADGeometry, + CutOffGeometry, + CutOffGeometryOrientationType, CutoffMaterialType, CutoffRuleType, CutoffSelectionRule, @@ -118,7 +120,6 @@ NodalDataType, OffsetDirectionType, OffsetType, - OrientationType, OrientedSelectionSet, OrientedSelectionSetElementalData, OrientedSelectionSetNodalData, @@ -142,6 +143,7 @@ Sensor, SensorType, SnapToGeometry, + SnapToGeometryOrientationType, SolidElementSet, SolidElementSetElementalData, SolidElementSetNodalData, @@ -196,6 +198,8 @@ "CADComponent", "CADGeometry", "ConnectLaunchConfig", + "CutOffGeometry", + "CutOffGeometryOrientationType", "CutoffMaterialType", "CutoffRuleType", "CutoffSelectionRule", @@ -274,7 +278,6 @@ "NodalDataType", "OffsetDirectionType", "OffsetType", - "OrientationType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", "OrientedSelectionSetNodalData", @@ -300,9 +303,10 @@ "Sensor", "SensorType", "SnapToGeometry", + "SnapToGeometryOrientationType", "SolidElementSet", - "SolidElementSetNodalData", "SolidElementSetElementalData", + "SolidElementSetNodalData", "SolidModel", "SolidModelElementalData", "SolidModelExportFormat", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 6cc1717603..f8c8a15df2 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -30,6 +30,7 @@ from .butt_joint_sequence import ButtJointSequence, PrimaryPly from .cad_component import CADComponent from .cad_geometry import CADGeometry, TriangleMesh +from .cut_off_geometry import CutOffGeometry from .cutoff_selection_rule import ( CutoffSelectionRule, CutoffSelectionRuleElementalData, @@ -45,6 +46,7 @@ from .enums import ( ArrowType, BooleanOperationType, + CutOffGeometryOrientationType, CutoffMaterialType, CutoffRuleType, DimensionType, @@ -68,7 +70,6 @@ NodalDataType, OffsetDirectionType, OffsetType, - OrientationType, PlyCutoffType, PlyGeometryExportFormat, PlyType, @@ -76,6 +77,7 @@ RosetteType, SectionCutType, SensorType, + SnapToGeometryOrientationType, SolidModelExportFormat, SolidModelSkinExportFormat, StatusType, @@ -173,6 +175,8 @@ "ButtJointSequence", "CADComponent", "CADGeometry", + "CutOffGeometry", + "CutOffGeometryOrientationType", "CutoffMaterialType", "CutoffRuleType", "CutoffSelectionRule", @@ -242,7 +246,6 @@ "NodalDataType", "OffsetDirectionType", "OffsetType", - "OrientationType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", "OrientedSelectionSetNodalData", @@ -267,6 +270,7 @@ "Sensor", "SensorType", "SnapToGeometry", + "SnapToGeometryOrientationType", "SolidElementSet", "SolidElementSetElementalData", "SolidElementSetNodalData", diff --git a/src/ansys/acp/core/_tree_objects/cut_off_geometry.py b/src/ansys/acp/core/_tree_objects/cut_off_geometry.py new file mode 100644 index 0000000000..4a096a9eb7 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/cut_off_geometry.py @@ -0,0 +1,106 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable + +from ansys.api.acp.v0 import cut_off_geometry_pb2, cut_off_geometry_pb2_grpc + +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .enums import ( + CutOffGeometryOrientationType, + cut_off_geometry_orientation_type_from_pb, + cut_off_geometry_orientation_type_to_pb, + status_type_from_pb, +) +from .object_registry import register +from .virtual_geometry import VirtualGeometry + +__all__ = ["CutOffGeometry"] + + +@mark_grpc_properties +@register +class CutOffGeometry(CreatableTreeObject, IdTreeObject): + """Instantiate a cut-off geometry. + + Parameters + ---------- + name : + Name of the cut-off geometry. + active : + Inactive cut-off geometries are not used in the solid model extrusion. + cad_geometry : + The geometry defining the cut-off. + orientation_type : + Determines the cutting orientation of a surface/body geometry. Allows to + switch between include and exclude. + relative_merge_tolerance : + Set the merging tolerance for neighboring nodes relative to the element size. + """ + + __slots__: Iterable[str] = () + + _COLLECTION_LABEL = "cut_off_geometries" + _OBJECT_INFO_TYPE = cut_off_geometry_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = cut_off_geometry_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "CutOffGeometry", + active: bool = True, + cad_geometry: VirtualGeometry | None = None, + orientation_type: CutOffGeometryOrientationType = CutOffGeometryOrientationType.UP, + relative_merge_tolerance: float = 0.1, + ): + super().__init__( + name=name, + ) + self.active = active + self.cad_geometry = cad_geometry + self.orientation_type = orientation_type + self.relative_merge_tolerance = relative_merge_tolerance + + def _create_stub(self) -> cut_off_geometry_pb2_grpc.ObjectServiceStub: + return cut_off_geometry_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + cad_geometry = grpc_link_property("properties.cad_geometry", allowed_types=(VirtualGeometry,)) + orientation_type = grpc_data_property( + "properties.orientation", + from_protobuf=cut_off_geometry_orientation_type_from_pb, + to_protobuf=cut_off_geometry_orientation_type_to_pb, + ) + relative_merge_tolerance: ReadWriteProperty[float, float] = grpc_data_property( + "properties.relative_merge_tolerance" + ) diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index 0a1914e2c7..873ec285a2 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -21,6 +21,7 @@ # SOFTWARE. from ansys.api.acp.v0 import ( + cut_off_geometry_pb2, cut_off_material_pb2, cutoff_selection_rule_pb2, drop_off_material_pb2, @@ -48,6 +49,7 @@ __all__ = [ "ArrowType", "BooleanOperationType", + "CutOffGeometryOrientationType", "CutoffMaterialType", "CutoffRuleType", "DimensionType", @@ -58,16 +60,18 @@ "EdgeSetType", "ElementalDataType", "ExtrusionGuideType", - "ExtrusionType", "ExtrusionMethodType", + "ExtrusionType", "GeometricalRuleType", + "ImportedPlyDrapingType", + "ImportedPlyOffsetType", + "ImportedPlyThicknessType", "IntersectionType", "LookUpTable3DInterpolationAlgorithm", "LookUpTableColumnValueType", "NodalDataType", "OffsetDirectionType", "OffsetType", - "OrientationType", "PlyCutoffType", "PlyGeometryExportFormat", "PlyType", @@ -75,17 +79,15 @@ "RosetteType", "SectionCutType", "SensorType", - "StatusType", - "SymmetryType", + "SnapToGeometryOrientationType", "SolidModelExportFormat", "SolidModelSkinExportFormat", + "StatusType", + "SymmetryType", "ThicknessFieldType", "ThicknessType", "UnitSystemType", "VirtualGeometryDimension", - "ImportedPlyDrapingType", - "ImportedPlyOffsetType", - "ImportedPlyThicknessType", ] (StatusType, status_type_to_pb, status_type_from_pb) = wrap_to_string_enum( @@ -536,8 +538,12 @@ def _prefix_undefined(value: str) -> str: return value -OrientationType, orientation_type_to_pb, orientation_type_from_pb = wrap_to_string_enum( - "OrientationType", +( + SnapToGeometryOrientationType, + snap_to_geometry_orientation_type_to_pb, + snap_to_geometry_orientation_type_from_pb, +) = wrap_to_string_enum( + "SnapToGeometryOrientationType", snap_to_geometry_pb2.OrientationType, module=__name__, key_converter=_prefix_undefined, @@ -548,3 +554,13 @@ def _prefix_undefined(value: str) -> str: "``BOTTOM``, and included only for compatibility with existing models." ), ) +( + CutOffGeometryOrientationType, + cut_off_geometry_orientation_type_to_pb, + cut_off_geometry_orientation_type_from_pb, +) = wrap_to_string_enum( + "CutOffGeometryOrientationType", + cut_off_geometry_pb2.OrientationType, + module=__name__, + doc="Determines the orientation of a cut-off geometry.", +) diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index cc9b952928..57f8e1eca3 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -27,6 +27,7 @@ from typing import Any from ansys.api.acp.v0 import ( + cut_off_geometry_pb2_grpc, enum_types_pb2, imported_solid_model_pb2, imported_solid_model_pb2_grpc, @@ -38,7 +39,11 @@ from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty from ._grpc_helpers.enum_wrapper import wrap_to_string_enum from ._grpc_helpers.exceptions import wrap_grpc_errors -from ._grpc_helpers.mapping import get_read_only_collection_property +from ._grpc_helpers.mapping import ( + define_create_method, + define_mutable_mapping, + get_read_only_collection_property, +) from ._grpc_helpers.property_helper import ( grpc_data_property, grpc_data_property_read_only, @@ -53,6 +58,7 @@ TreeObjectAttributeWithCache, nested_grpc_object_property, ) +from .cut_off_geometry import CutOffGeometry from .enums import ( UnitSystemType, status_type_from_pb, @@ -320,6 +326,16 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: elemental_data = elemental_data_property(ImportedSolidModelElementalData) nodal_data = nodal_data_property(ImportedSolidModelNodalData) + create_cut_off_geometry = define_create_method( + CutOffGeometry, + func_name="create_cut_off_geometry", + parent_class_name="ImportedSolidModel", + module_name=__module__, + ) + cut_off_geometries = define_mutable_mapping( + CutOffGeometry, cut_off_geometry_pb2_grpc.ObjectServiceStub + ) + def refresh(self) -> None: """Re-import the solid model from the external file.""" with wrap_grpc_errors(): diff --git a/src/ansys/acp/core/_tree_objects/snap_to_geometry.py b/src/ansys/acp/core/_tree_objects/snap_to_geometry.py index 860410e237..5a4e5fd8e1 100644 --- a/src/ansys/acp/core/_tree_objects/snap_to_geometry.py +++ b/src/ansys/acp/core/_tree_objects/snap_to_geometry.py @@ -35,9 +35,9 @@ ) from .base import CreatableTreeObject, IdTreeObject from .enums import ( - OrientationType, - orientation_type_from_pb, - orientation_type_to_pb, + SnapToGeometryOrientationType, + snap_to_geometry_orientation_type_from_pb, + snap_to_geometry_orientation_type_to_pb, status_type_from_pb, ) from .object_registry import register @@ -81,7 +81,7 @@ def __init__( *, name: str = "SnapToGeometry", active: bool = True, - orientation_type: OrientationType = OrientationType.TOP, + orientation_type: SnapToGeometryOrientationType = SnapToGeometryOrientationType.TOP, cad_geometry: VirtualGeometry | None = None, oriented_selection_set: OrientedSelectionSet | None = None, ): @@ -98,8 +98,8 @@ def _create_stub(self) -> snap_to_geometry_pb2_grpc.ObjectServiceStub: active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") orientation_type = grpc_data_property( "properties.orientation_type", - from_protobuf=orientation_type_from_pb, - to_protobuf=orientation_type_to_pb, + from_protobuf=snap_to_geometry_orientation_type_from_pb, + to_protobuf=snap_to_geometry_orientation_type_to_pb, ) cad_geometry = grpc_link_property("properties.cad_geometry", allowed_types=(VirtualGeometry,)) oriented_selection_set = grpc_link_property( diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index 5a69149ada..96ccb685a2 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -27,6 +27,7 @@ from typing import Any from ansys.api.acp.v0 import ( + cut_off_geometry_pb2_grpc, extrusion_guide_pb2_grpc, snap_to_geometry_pb2_grpc, solid_element_set_pb2_grpc, @@ -64,6 +65,7 @@ TreeObjectAttributeWithCache, nested_grpc_object_property, ) +from .cut_off_geometry import CutOffGeometry from .edge_set import EdgeSet from .element_set import ElementSet from .enums import ( @@ -513,5 +515,15 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: SolidElementSet, solid_element_set_pb2_grpc.ObjectServiceStub ) + create_cut_off_geometry = define_create_method( + CutOffGeometry, + func_name="create_cut_off_geometry", + parent_class_name="SolidModel", + module_name=__module__, + ) + cut_off_geometries = define_mutable_mapping( + CutOffGeometry, cut_off_geometry_pb2_grpc.ObjectServiceStub + ) + elemental_data = elemental_data_property(SolidModelElementalData) nodal_data = nodal_data_property(SolidModelNodalData) diff --git a/tests/unittests/test_cut_off_geometry.py b/tests/unittests/test_cut_off_geometry.py new file mode 100644 index 0000000000..3da4df9525 --- /dev/null +++ b/tests/unittests/test_cut_off_geometry.py @@ -0,0 +1,85 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import CutOffGeometry, CutOffGeometryOrientationType + +from .common.tree_object_tester import ObjectPropertiesToTest, TreeObjectTester, WithLockedMixin + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version(CutOffGeometry._SUPPORTED_SINCE): + pytest.skip("CutOffGeometry is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture(params=["create_solid_model", "create_imported_solid_model"]) +def parent_object(model, request): + return getattr(model, request.param)() + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_cut_off_geometry() + + +class TestCutOffGeometry(WithLockedMixin, TreeObjectTester): + COLLECTION_NAME = "cut_off_geometries" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "active": True, + "cad_geometry": None, + "orientation_type": CutOffGeometryOrientationType.UP, + "relative_merge_tolerance": 0.1, + } + + CREATE_METHOD_NAME = "create_cut_off_geometry" + INITIAL_OBJECT_NAMES = tuple() + + @staticmethod + @pytest.fixture + def object_properties(model): + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_cut_off_geometry"), + ("active", False), + ("cad_geometry", model.create_virtual_geometry()), + ("orientation_type", CutOffGeometryOrientationType.DOWN), + ("relative_merge_tolerance", 0.1257), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) diff --git a/tests/unittests/test_snap_to_geometry.py b/tests/unittests/test_snap_to_geometry.py index d8235ea7a0..6a5f0627f4 100644 --- a/tests/unittests/test_snap_to_geometry.py +++ b/tests/unittests/test_snap_to_geometry.py @@ -23,7 +23,7 @@ from packaging.version import parse as parse_version import pytest -from ansys.acp.core import OrientationType, SnapToGeometry +from ansys.acp.core import SnapToGeometry, SnapToGeometryOrientationType from .common.tree_object_tester import ObjectPropertiesToTest, TreeObjectTester, WithLockedMixin @@ -59,7 +59,7 @@ def default_properties(): return { "status": "NOTUPTODATE", "active": True, - "orientation_type": OrientationType.TOP, + "orientation_type": SnapToGeometryOrientationType.TOP, "cad_geometry": None, "oriented_selection_set": None, } @@ -74,7 +74,7 @@ def object_properties(model): read_write=[ ("name", "new_snap_to_geometry"), ("active", False), - ("orientation_type", OrientationType.BOTTOM), + ("orientation_type", SnapToGeometryOrientationType.BOTTOM), ("cad_geometry", model.create_virtual_geometry()), ("oriented_selection_set", model.create_oriented_selection_set()), ], From b198ad03fc5dedc860e40ad740a6aac8648ee198 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 17:22:10 +0100 Subject: [PATCH 50/96] Use ansys.tools.path for getting the latest Ansys install (#657) Use the `get_latest_ansys_installation` from `ansys.tools.path` instead of our own implementation. The logic we had used to select the latest version has been upstreamed to `ansys.tools.path`, so there is now no need to keep it in our codebase anymore. Closes #360. --- src/ansys/acp/core/_server/direct.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/ansys/acp/core/_server/direct.py b/src/ansys/acp/core/_server/direct.py index e96c851fdb..8190986e6a 100644 --- a/src/ansys/acp/core/_server/direct.py +++ b/src/ansys/acp/core/_server/direct.py @@ -35,33 +35,16 @@ LauncherProtocol, ServerType, ) -from ansys.tools.path import get_available_ansys_installations +from ansys.tools.path import get_latest_ansys_installation from .common import ServerKey __all__ = ["DirectLaunchConfig"] -def _get_latest_ansys_installation() -> str: - """Get the latest installed Ansys installation.""" - - installations = get_available_ansys_installations() - if not installations: - raise ValueError("No Ansys installation found.") - - def sort_key(version_nr: int) -> int | float: - # prefer regular over student installs - if version_nr < 0: - return abs(version_nr) - 0.5 - return version_nr - - latest_key = max(installations, key=sort_key) - return installations[latest_key] - - def _get_default_binary_path() -> str: try: - ans_root = _get_latest_ansys_installation() + _, ans_root = get_latest_ansys_installation() binary_path = os.path.join(ans_root, "ACP", "acp_grpcserver") if os.name == "nt": binary_path += ".exe" From 55bd01fca16c086e6a8b087888e39c7235c83d06 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 6 Nov 2024 23:48:37 +0100 Subject: [PATCH 51/96] Add LayupMappingObject (#649) Add exposure for the `LayupMappingObject`. Improve the type annotations in `linked_object_list` to allow `SolidElementSet` to be used as value type. --- .pre-commit-config.yaml | 10 +- doc/source/api/enum_types.rst | 7 +- doc/source/api/tree_objects.rst | 1 + pyproject.toml | 2 +- src/ansys/acp/core/__init__.py | 14 +- src/ansys/acp/core/_tree_objects/__init__.py | 13 +- .../_grpc_helpers/linked_object_list.py | 17 +- src/ansys/acp/core/_tree_objects/enums.py | 41 +++ .../_tree_objects/imported_solid_model.py | 13 + .../_tree_objects/layup_mapping_object.py | 313 ++++++++++++++++++ tests/unittests/test_layup_mapping_object.py | 147 ++++++++ 11 files changed, 561 insertions(+), 17 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/layup_mapping_object.py create mode 100644 tests/unittests/test_layup_mapping_object.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3eb20eccb2..fc6a80a6f4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,20 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v3.17.0 + rev: v3.19.0 hooks: - id: pyupgrade args: [--py310-plus] - repo: https://github.com/psf/black - rev: "24.8.0" # when changed, also update the version in blacken-docs + rev: "24.10.0" # when changed, also update the version in blacken-docs hooks: - id: black - repo: https://github.com/adamchainz/blacken-docs - rev: 1.18.0 + rev: 1.19.1 hooks: - id: blacken-docs - additional_dependencies: [black==24.8.0] + additional_dependencies: [black==24.10.0] - repo: https://github.com/pycqa/isort rev: "5.13.2" @@ -51,7 +51,7 @@ repos: exclude: "^(tests/)|(type_checks/)|(examples/)" - repo: https://github.com/kynan/nbstripout - rev: 0.7.1 + rev: 0.8.0 hooks: - id: nbstripout args: diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 1c87ce4e9f..23125ba507 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -8,6 +8,7 @@ Enumeration data types :template: autosummary/no_methods_doc/class.rst.jinja2 ArrowType + BaseElementMaterialHandling BooleanOperationType CutOffGeometryOrientationType CutoffMaterialType @@ -19,9 +20,10 @@ Enumeration data types DropOffType EdgeSetType ElementalDataType + ElementTechnology ExtrusionGuideType - ExtrusionType ExtrusionMethodType + ExtrusionType FeFormat GeometricalRuleType IgnorableEntity @@ -29,6 +31,7 @@ Enumeration data types ImportedPlyOffsetType ImportedPlyThicknessType IntersectionType + LayupMappingRosetteSelectionMethod LinkedObjectHandling LookUpTable3DInterpolationAlgorithm LookUpTableColumnValueType @@ -40,6 +43,7 @@ Enumeration data types PlyCutoffType PlyGeometryExportFormat PlyType + ReinforcingBehavior RosetteSelectionMethod RosetteType SectionCutType @@ -48,6 +52,7 @@ Enumeration data types SolidModelImportFormat SolidModelSkinExportFormat StatusType + StressStateType SymmetryType ThicknessFieldType ThicknessType diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 83462e73e2..ebf70cd405 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -28,6 +28,7 @@ ACP objects ImportedSolidModel ImportedSolidModelExportSettings InterfaceLayer + LayupMappingObject LookUpTable1D LookUpTable1DColumn LookUpTable3D diff --git a/pyproject.toml b/pyproject.toml index f1072ba834..d8917eac09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,7 +118,7 @@ priority = "primary" [tool.black] line-length = 100 -target-version = ['py38'] +target-version = ['py310'] [tool.isort] profile = "black" diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 978c54fa3b..54c33f677d 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -44,6 +44,7 @@ AnalysisPlyElementalData, AnalysisPlyNodalData, ArrowType, + BaseElementMaterialHandling, BooleanOperationType, BooleanSelectionRule, BooleanSelectionRuleElementalData, @@ -73,6 +74,7 @@ ElementSet, ElementSetElementalData, ElementSetNodalData, + ElementTechnology, ExtrusionGuide, ExtrusionGuideType, ExtrusionMethodType, @@ -100,6 +102,8 @@ InterfaceLayer, IntersectionType, Lamina, + LayupMappingObject, + LayupMappingRosetteSelectionMethod, LinkedSelectionRule, LookUpTable1D, LookUpTable1DColumn, @@ -133,6 +137,7 @@ ProductionPly, ProductionPlyElementalData, ProductionPlyNodalData, + ReinforcingBehavior, Rosette, RosetteSelectionMethod, RosetteType, @@ -159,6 +164,7 @@ SphericalSelectionRuleNodalData, Stackup, StatusType, + StressStateType, SubLaminate, SubShape, SymmetryType, @@ -190,6 +196,7 @@ "AnalysisPlyElementalData", "AnalysisPlyNodalData", "ArrowType", + "BaseElementMaterialHandling", "BooleanOperationType", "BooleanSelectionRule", "BooleanSelectionRuleElementalData", @@ -222,6 +229,7 @@ "ElementSet", "ElementSetElementalData", "ElementSetNodalData", + "ElementTechnology", "example_helpers", "ExtrusionGuide", "ExtrusionGuideType", @@ -229,8 +237,8 @@ "ExtrusionType", "Fabric", "FabricWithAngle", - "FieldDefinition", "FeFormat", + "FieldDefinition", "GeometricalRuleType", "GeometricalSelectionRule", "GeometricalSelectionRuleElementalData", @@ -256,6 +264,8 @@ "Lamina", "launch_acp", "LaunchMode", + "LayupMappingObject", + "LayupMappingRosetteSelectionMethod", "LinkedObjectHandling", "LinkedSelectionRule", "LookUpTable1D", @@ -293,6 +303,7 @@ "ProductionPlyElementalData", "ProductionPlyNodalData", "recursive_copy", + "ReinforcingBehavior", "Rosette", "RosetteSelectionMethod", "RosetteType", @@ -319,6 +330,7 @@ "SphericalSelectionRuleNodalData", "Stackup", "StatusType", + "StressStateType", "SubLaminate", "SubShape", "SymmetryType", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index f8c8a15df2..34c560286a 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -45,6 +45,7 @@ from .element_set import ElementSet, ElementSetElementalData, ElementSetNodalData from .enums import ( ArrowType, + BaseElementMaterialHandling, BooleanOperationType, CutOffGeometryOrientationType, CutoffMaterialType, @@ -56,6 +57,7 @@ DropOffType, EdgeSetType, ElementalDataType, + ElementTechnology, ExtrusionGuideType, ExtrusionMethodType, ExtrusionType, @@ -73,6 +75,7 @@ PlyCutoffType, PlyGeometryExportFormat, PlyType, + ReinforcingBehavior, RosetteSelectionMethod, RosetteType, SectionCutType, @@ -81,6 +84,7 @@ SolidModelExportFormat, SolidModelSkinExportFormat, StatusType, + StressStateType, SymmetryType, ThicknessFieldType, ThicknessType, @@ -107,6 +111,7 @@ SolidModelImportFormat, ) from .interface_layer import InterfaceLayer +from .layup_mapping_object import LayupMappingObject, LayupMappingRosetteSelectionMethod from .linked_selection_rule import LinkedSelectionRule from .lookup_table_1d import LookUpTable1D from .lookup_table_1d_column import LookUpTable1DColumn @@ -168,6 +173,7 @@ "AnalysisPlyElementalData", "AnalysisPlyNodalData", "ArrowType", + "BaseElementMaterialHandling", "BooleanOperationType", "BooleanSelectionRule", "BooleanSelectionRuleElementalData", @@ -197,14 +203,15 @@ "ElementSet", "ElementSetElementalData", "ElementSetNodalData", + "ElementTechnology", "ExtrusionGuide", "ExtrusionGuideType", "ExtrusionMethodType", "ExtrusionType", "Fabric", "FabricWithAngle", - "FieldDefinition", "FeFormat", + "FieldDefinition", "FieldVariable", "GeometricalRuleType", "GeometricalSelectionRule", @@ -226,6 +233,8 @@ "InterpolationOptions", "IntersectionType", "Lamina", + "LayupMappingObject", + "LayupMappingRosetteSelectionMethod", "LinkedSelectionRule", "LookUpTable1D", "LookUpTable1DColumn", @@ -260,6 +269,7 @@ "ProductionPlyElementalData", "ProductionPlyNodalData", "PuckMaterialType", + "ReinforcingBehavior", "Rosette", "RosetteSelectionMethod", "RosetteType", @@ -286,6 +296,7 @@ "SphericalSelectionRuleNodalData", "Stackup", "StatusType", + "StressStateType", "SubLaminate", "SubShape", "SymmetryType", diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py index eabe1e5f4e..86bc86d7a8 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/linked_object_list.py @@ -34,11 +34,11 @@ from ansys.api.acp.v0.base_pb2 import ResourcePath from .._object_cache import ObjectCacheMixin, constructor_with_cache -from ..base import CreatableTreeObject, TreeObject +from ..base import TreeObject, TreeObjectBase from .polymorphic_from_pb import tree_object_from_resource_path from .property_helper import _exposed_grpc_property, _wrap_doc, grpc_data_getter, grpc_data_setter -ValueT = TypeVar("ValueT", bound=CreatableTreeObject) +ValueT = TypeVar("ValueT", bound=TreeObjectBase) __all__ = ["LinkedObjectList", "define_linked_object_list", "define_polymorphic_linked_object_list"] @@ -143,7 +143,7 @@ def __setitem__(self, key: slice, value: Iterable[ValueT]) -> None: ... def __setitem__(self, key: int | slice, value: ValueT | Iterable[ValueT]) -> None: resource_path_list = self._get_resourcepath_list() - if isinstance(value, TreeObject): + if isinstance(value, TreeObjectBase): self._check_type(value) if not isinstance(key, int): raise TypeError("Cannot assign to a slice with a single object.") @@ -298,7 +298,8 @@ def _check_type(self, object: Any) -> None: ) -ChildT = TypeVar("ChildT", bound=CreatableTreeObject) +ParentT = TypeVar("ParentT", bound=TreeObject) +ChildT = TypeVar("ChildT", bound=TreeObjectBase) def define_linked_object_list( @@ -306,7 +307,7 @@ def define_linked_object_list( ) -> Any: """Define a list of linked tree objects.""" - def getter(self: ValueT) -> LinkedObjectList[ChildT]: + def getter(self: ParentT) -> LinkedObjectList[ChildT]: return LinkedObjectList._initialize_with_cache( parent_object=self, attribute_name=attribute_name, @@ -314,7 +315,7 @@ def getter(self: ValueT) -> LinkedObjectList[ChildT]: allowed_types=(object_class,), ) - def setter(self: ValueT, value: list[ChildT]) -> None: + def setter(self: ParentT, value: list[ChildT]) -> None: getter(self)[:] = value return _wrap_doc(_exposed_grpc_property(getter).setter(setter), doc=doc) @@ -329,7 +330,7 @@ def define_polymorphic_linked_object_list( if allowed_types is None != allowed_types_getter is None: raise ValueError("Exactly one of allowed_types and allowed_types_getter must be provided.") - def getter(self: ValueT) -> LinkedObjectList[Any]: + def getter(self: ParentT) -> LinkedObjectList[Any]: nonlocal allowed_types if allowed_types_getter is not None: allowed_types = allowed_types_getter() @@ -344,7 +345,7 @@ def getter(self: ValueT) -> LinkedObjectList[Any]: _allowed_types=allowed_types, ) - def setter(self: ValueT, value: list[Any]) -> None: + def setter(self: ParentT, value: list[Any]) -> None: getter(self)[:] = value return _exposed_grpc_property(getter).setter(setter) diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index 873ec285a2..76456e3b80 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -30,6 +30,7 @@ extrusion_guide_pb2, geometrical_selection_rule_pb2, imported_modeling_ply_pb2, + layup_mapping_object_pb2, lookup_table_3d_pb2, lookup_table_column_type_pb2, mesh_query_pb2, @@ -59,6 +60,10 @@ "DropOffType", "EdgeSetType", "ElementalDataType", + "ElementTechnology", + "ReinforcingBehavior", + "BaseElementMaterialHandling", + "StressStateType", "ExtrusionGuideType", "ExtrusionMethodType", "ExtrusionType", @@ -564,3 +569,39 @@ def _prefix_undefined(value: str) -> str: module=__name__, doc="Determines the orientation of a cut-off geometry.", ) + +ElementTechnology, element_technology_to_pb, element_technology_from_pb = wrap_to_string_enum( + "ElementTechnology", + layup_mapping_object_pb2.ElementTechnology, + module=__name__, + doc=("Options for the element technology used in a layup mapping object."), +) + +ReinforcingBehavior, reinforcing_behavior_to_pb, reinforcing_behavior_from_pb = wrap_to_string_enum( + "ReinforcingBehavior", + layup_mapping_object_pb2.ReinforcingBehavior, + module=__name__, + doc=( + "Specifies whether the reinforcing elements carry tension and compression load, or only one of them." + ), +) + +( + BaseElementMaterialHandling, + base_element_material_handling_to_pb, + base_element_material_handling_from_pb, +) = wrap_to_string_enum( + "BaseElementMaterialHandling", + layup_mapping_object_pb2.BaseElementMaterialHandlingType, + module=__name__, + doc=( + "Determines how the base material is handled where it intersects with a reinforcing element." + ), +) +StressStateType, stress_state_type_to_pb, stress_state_type_from_pb = wrap_to_string_enum( + "StressStateType", + layup_mapping_object_pb2.StressStateType, + module=__name__, + doc="Specifies if the reinforcing elements should behave like a link, membrane, or shell " + "element (with or without bending).", +) diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index 57f8e1eca3..dc36bd9772 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -31,6 +31,7 @@ enum_types_pb2, imported_solid_model_pb2, imported_solid_model_pb2_grpc, + layup_mapping_object_pb2_grpc, solid_element_set_pb2_grpc, solid_model_pb2, ) @@ -65,6 +66,7 @@ unit_system_type_from_pb, unit_system_type_to_pb, ) +from .layup_mapping_object import LayupMappingObject from .material import Material from .object_registry import register from .solid_element_set import SolidElementSet @@ -336,6 +338,17 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: CutOffGeometry, cut_off_geometry_pb2_grpc.ObjectServiceStub ) + create_layup_mapping_object = define_create_method( + LayupMappingObject, + func_name="create_layup_mapping_object", + parent_class_name="ImportedSolidModel", + module_name=__name__, + ) + layup_mapping_objects = define_mutable_mapping( + LayupMappingObject, + layup_mapping_object_pb2_grpc.ObjectServiceStub, + ) + def refresh(self) -> None: """Re-import the solid model from the external file.""" with wrap_grpc_errors(): diff --git a/src/ansys/acp/core/_tree_objects/layup_mapping_object.py b/src/ansys/acp/core/_tree_objects/layup_mapping_object.py new file mode 100644 index 0000000000..45095c929a --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/layup_mapping_object.py @@ -0,0 +1,313 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +from collections.abc import Iterable, Sequence + +from ansys.api.acp.v0 import enum_types_pb2, layup_mapping_object_pb2, layup_mapping_object_pb2_grpc + +from .._utils.property_protocols import ReadWriteProperty +from ._grpc_helpers.enum_wrapper import wrap_to_string_enum +from ._grpc_helpers.linked_object_list import ( + define_linked_object_list, + define_polymorphic_linked_object_list, +) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from .base import CreatableTreeObject, IdTreeObject +from .element_set import ElementSet +from .enums import ( + BaseElementMaterialHandling, + ElementTechnology, + ReinforcingBehavior, + StressStateType, + base_element_material_handling_from_pb, + base_element_material_handling_to_pb, + element_technology_from_pb, + element_technology_to_pb, + reinforcing_behavior_from_pb, + reinforcing_behavior_to_pb, + status_type_from_pb, + stress_state_type_from_pb, + stress_state_type_to_pb, +) +from .imported_modeling_group import ImportedModelingGroup +from .imported_modeling_ply import ImportedModelingPly +from .material import Material +from .modeling_group import ModelingGroup +from .modeling_ply import ModelingPly +from .object_registry import register +from .rosette import Rosette +from .solid_element_set import SolidElementSet + +__all__ = ["LayupMappingObject", "LayupMappingRosetteSelectionMethod"] + + +( + LayupMappingRosetteSelectionMethod, + layup_mapping_rosette_selection_method_to_pb, + layup_mapping_rosette_selection_method_from_pb, +) = wrap_to_string_enum( + "LayupMappingRosetteSelectionMethod", + enum_types_pb2.RosetteSelectionMethod, + module=__name__, + doc="Options for how the rosette is selected in the layup mapping.", + explicit_value_list=( + enum_types_pb2.RosetteSelectionMethod.MINIMUM_DISTANCE, + enum_types_pb2.RosetteSelectionMethod.MINIMUM_DISTANCE_SUPERPOSED, + ), +) + + +@mark_grpc_properties +@register +class LayupMappingObject(CreatableTreeObject, IdTreeObject): + """Instantiate a layup mapping object. + + Parameters + ---------- + name : + Name of the layup mapping object. + active : + Inactive layup mapping objects are not used. + element_technology : + Determines the element technology used for the layup mapping. + Can be either ``"layered_element"`` or ``"reinforcing"``. + Note that only brick and prism elements support the layered option, + while reinforcing technology can be combined with all types of + solid elements. + shell_element_sets : + Defines the shell area whose modeling plies are mapped onto the + solid mesh. + Used only if ``use_imported_plies`` is False. + use_imported_plies : + If True, imported modeling plies are mapped onto the solid mesh. + select_all_plies : + Determines whether all plies are selected for mapping, or only + the ones specified in the ``sequences`` parameter. + + * If ``use_imported_plies`` is False and ``select_all_plies`` is True, + all plies in the selected shell area are selected. + * If ``use_imported_plies`` is True and ``select_all_plies`` is True, + all imported modeling plies are selected. + + sequences : + Determines which plies are considered for mapping. The intersection + of shell elements and plies are then mapped onto the solid mesh. + + * If ``use_imported_plies`` is False, modeling groups and modeling + plies are allowed + * If ``use_imported_plies`` is True, imported modeling groups and + imported modeling plies are allowed + + entire_solid_mesh : + If True, the selected layup is mapped onto the entire solid mesh. + Otherwise, the ``solid_element_sets`` parameter is used to refine + the scope. + solid_element_sets : + List of solid element sets to which the mapping is applied. + scale_ply_thicknesses : + If True, scale the layer thickness to fill the gaps within an element, + instead of filling them with the void material. + Used only if ``element_technology`` is ``"layered_element"``. + void_material : + Material used to fill the gaps with. + Only used if ``scale_ply_thicknesses`` is False, and + the ``element_technology`` is ``"layered_element"``. + minimum_void_material_thickness : + Only gaps with a thickness greater than this value are filled with + the void material. + Only used if ``scale_ply_thicknesses`` is False, and + the ``element_technology`` is ``"layered_element"``. + delete_lost_elements : + If True, elements without layup and degenerated elements are deleted. + Otherwise, they are filled with the filler material. + Used only if ``element_technology`` is ``"layered_element"``. + filler_material : + Material used to fill the degenerated elements. + Only used if ``delete_lost_elements`` is False, and + the ``element_technology`` is ``"layered_element"``. + rosettes : + List of rosettes to set the coordinate system of the filler elements. + Used only if ``element_technology`` is ``"layered_element"``. + rosette_selection_method : + Defines how the coordinate systems are applied for the filler elements. + Used only if ``element_technology`` is ``"layered_element"``. + reinforcing_behavior : + Determines whether the reinforcing elements carry tension and / or + compression loads. + Used only if ``element_technology`` is ``"reinforcing"``. + base_element_material_handling : + Determines how the base material is handled in the area with reinforcing + elements. Can be either ``"remove"`` or ``"retain"``. + Used only if ``element_technology`` is ``"reinforcing"``. + stress_state : + Specifies the stress state of the reinforcing elements. + Used only if ``element_technology`` is ``"reinforcing"``. + base_material : + Define the initial material of the solid elements which will be reinforced + by the selected plies. + Used only if ``element_technology`` is ``"reinforcing"``. + base_element_rosettes : + List of rosettes to set the coordinate system of the base elements. + This is important if the base material has an orthotropic characteristic. + Used only if ``element_technology`` is ``"reinforcing"``. + base_element_rosette_selection_method : + Defines how the coordinate systems are applied for the base elements. + Used only if ``element_technology`` is ``"reinforcing"``. + + """ + + __slots__: Iterable[str] = tuple() + + _COLLECTION_LABEL = "layup_mapping_objects" + _OBJECT_INFO_TYPE = layup_mapping_object_pb2.ObjectInfo + _CREATE_REQUEST_TYPE = layup_mapping_object_pb2.CreateRequest + _SUPPORTED_SINCE = "25.1" + + def __init__( + self, + *, + name: str = "LayupMappingObject", + active: bool = True, + element_technology: ElementTechnology = ElementTechnology.LAYERED_ELEMENT, + shell_element_sets: Sequence[ElementSet] = (), + use_imported_plies: bool = False, + select_all_plies: bool = True, + sequences: Sequence[ + ModelingGroup | ModelingPly | ImportedModelingGroup | ImportedModelingPly + ] = (), + entire_solid_mesh: bool = True, + solid_element_sets: Sequence[SolidElementSet] = (), + scale_ply_thicknesses: bool = True, + void_material: Material | None = None, + minimum_void_material_thickness: float = 1e-6, + delete_lost_elements: bool = True, + filler_material: Material | None = None, + rosettes: Sequence[Rosette] = (), + rosette_selection_method: LayupMappingRosetteSelectionMethod = LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, # type: ignore # noqa: E501 + reinforcing_behavior: ReinforcingBehavior = ReinforcingBehavior.TENSION_AND_COMPRESSION, + base_element_material_handling: BaseElementMaterialHandling = BaseElementMaterialHandling.REMOVE, # noqa: E501 + stress_state: StressStateType = StressStateType.PLANE_STRESS_STATE_WITH_TRANSVERSE_SHEAR_AND_BENDING_STIFFNESS, # noqa: E501 + base_material: Material | None = None, + base_element_rosettes: Sequence[Rosette] = (), + base_element_rosette_selection_method: LayupMappingRosetteSelectionMethod = LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, # type: ignore # noqa: E501 + ): + super().__init__(name=name) + self.active = active + self.element_technology = element_technology + self.shell_element_sets = shell_element_sets + self.use_imported_plies = use_imported_plies + self.select_all_plies = select_all_plies + self.sequences = sequences + self.entire_solid_mesh = entire_solid_mesh + self.solid_element_sets = solid_element_sets + self.scale_ply_thicknesses = scale_ply_thicknesses + self.void_material = void_material + self.minimum_void_material_thickness = minimum_void_material_thickness + self.delete_lost_elements = delete_lost_elements + self.filler_material = filler_material + self.rosettes = rosettes + self.rosette_selection_method = rosette_selection_method + self.reinforcing_behavior = reinforcing_behavior + self.base_element_material_handling = base_element_material_handling + self.stress_state = stress_state + self.base_material = base_material + self.base_element_rosettes = base_element_rosettes + self.base_element_rosette_selection_method = base_element_rosette_selection_method + + def _create_stub(self) -> layup_mapping_object_pb2_grpc.ObjectServiceStub: + return layup_mapping_object_pb2_grpc.ObjectServiceStub(self._channel) + + status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) + + active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") + element_technology = grpc_data_property( + "properties.element_technology", + from_protobuf=element_technology_from_pb, + to_protobuf=element_technology_to_pb, + ) + shell_element_sets = define_linked_object_list( + "properties.shell_element_sets", object_class=ElementSet + ) + use_imported_plies: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.use_imported_plies" + ) + select_all_plies: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.select_all_plies" + ) + sequences = define_polymorphic_linked_object_list( + "properties.sequences", + allowed_types=(ModelingGroup, ModelingPly, ImportedModelingGroup, ImportedModelingPly), + ) + entire_solid_mesh: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.entire_solid_mesh" + ) + solid_element_sets = define_linked_object_list( + "properties.solid_element_sets", object_class=SolidElementSet + ) + scale_ply_thicknesses: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.scale_ply_thicknesses" + ) + void_material = grpc_link_property("properties.void_material", allowed_types=(Material,)) + minimum_void_material_thickness: ReadWriteProperty[float, float] = grpc_data_property( + "properties.minimum_void_material_thickness" + ) + delete_lost_elements: ReadWriteProperty[bool, bool] = grpc_data_property( + "properties.delete_lost_elements" + ) + filler_material = grpc_link_property("properties.filler_material", allowed_types=(Material,)) + rosettes = define_linked_object_list("properties.rosettes", object_class=Rosette) + rosette_selection_method = grpc_data_property( + "properties.rosette_selection_method", + from_protobuf=layup_mapping_rosette_selection_method_from_pb, + to_protobuf=layup_mapping_rosette_selection_method_to_pb, + ) + reinforcing_behavior = grpc_data_property( + "properties.reinforcing_behavior", + from_protobuf=reinforcing_behavior_from_pb, + to_protobuf=reinforcing_behavior_to_pb, + ) + base_element_material_handling = grpc_data_property( + "properties.base_element_material_handling", + from_protobuf=base_element_material_handling_from_pb, + to_protobuf=base_element_material_handling_to_pb, + ) + stress_state = grpc_data_property( + "properties.stress_state", + from_protobuf=stress_state_type_from_pb, + to_protobuf=stress_state_type_to_pb, + ) + base_material = grpc_link_property("properties.base_material", allowed_types=(Material,)) + base_element_rosettes = define_linked_object_list( + "properties.base_element_rosettes", object_class=Rosette + ) + base_element_rosette_selection_method = grpc_data_property( + "properties.base_element_rosette_selection_method", + from_protobuf=layup_mapping_rosette_selection_method_from_pb, + to_protobuf=layup_mapping_rosette_selection_method_to_pb, + ) diff --git a/tests/unittests/test_layup_mapping_object.py b/tests/unittests/test_layup_mapping_object.py new file mode 100644 index 0000000000..a9cd3d115f --- /dev/null +++ b/tests/unittests/test_layup_mapping_object.py @@ -0,0 +1,147 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from packaging.version import parse as parse_version +import pytest + +from ansys.acp.core import ( + BaseElementMaterialHandling, + ElementTechnology, + LayupMappingObject, + LayupMappingRosetteSelectionMethod, + ReinforcingBehavior, + StressStateType, +) + +from .common.tree_object_tester import ObjectPropertiesToTest, TreeObjectTester, WithLockedMixin + + +@pytest.fixture(autouse=True) +def skip_if_unsupported_version(acp_instance): + if parse_version(acp_instance.server_version) < parse_version( + LayupMappingObject._SUPPORTED_SINCE + ): + pytest.skip("LayupMappingObject is not supported on this version of the server.") + + +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +@pytest.fixture +def parent_object(model): + return model.create_imported_solid_model() + + +@pytest.fixture +def tree_object(parent_object): + return parent_object.create_layup_mapping_object() + + +class TestLayupMappingObject(WithLockedMixin, TreeObjectTester): + COLLECTION_NAME = "layup_mapping_objects" + + @staticmethod + @pytest.fixture + def default_properties(): + return { + "status": "NOTUPTODATE", + "active": True, + "element_technology": ElementTechnology.LAYERED_ELEMENT, + "shell_element_sets": tuple(), + "use_imported_plies": False, + "select_all_plies": True, + "sequences": tuple(), + "entire_solid_mesh": True, + "solid_element_sets": tuple(), + "scale_ply_thicknesses": True, + "void_material": None, + "minimum_void_material_thickness": 1e-6, + "delete_lost_elements": True, + "filler_material": None, + "rosettes": tuple(), + "rosette_selection_method": LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, + "reinforcing_behavior": ReinforcingBehavior.TENSION_AND_COMPRESSION, + "base_element_material_handling": BaseElementMaterialHandling.REMOVE, + "stress_state": StressStateType.PLANE_STRESS_STATE_WITH_TRANSVERSE_SHEAR_AND_BENDING_STIFFNESS, + "base_material": None, + "base_element_rosettes": tuple(), + "base_element_rosette_selection_method": LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, + } + + CREATE_METHOD_NAME = "create_layup_mapping_object" + INITIAL_OBJECT_NAMES = tuple() + + @staticmethod + @pytest.fixture + def object_properties(model): + modeling_group = model.create_modeling_group() + imported_modeling_group = model.create_imported_modeling_group() + return ObjectPropertiesToTest( + read_write=[ + ("name", "new_layup_mapping_object"), + ("active", False), + ("element_technology", ElementTechnology.REINFORCING), + ("shell_element_sets", [model.create_element_set() for _ in range(3)]), + ("select_all_plies", False), + ( + "sequences", + [modeling_group.create_modeling_ply(), model.create_modeling_group()], + ), + ("use_imported_plies", True), + ( + "sequences", + [ + imported_modeling_group.create_imported_modeling_ply(), + model.create_imported_modeling_group(), + ], + ), + ("entire_solid_mesh", False), + # Note: solid_element_sets is not tested because we would need to + # create a fully-defined solid model + ("scale_ply_thicknesses", False), + ("void_material", model.create_material()), + ("minimum_void_material_thickness", 1e-3), + ("delete_lost_elements", False), + ("filler_material", model.create_material()), + ("rosettes", [model.create_rosette() for _ in range(3)]), + ( + "rosette_selection_method", + LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE_SUPERPOSED, + ), + ("reinforcing_behavior", ReinforcingBehavior.TENSION_ONLY), + ("base_element_material_handling", BaseElementMaterialHandling.RETAIN), + ("stress_state", StressStateType.PLANE_STRESS_STATE), + ("base_material", model.create_material()), + ("base_element_rosettes", [model.create_rosette() for _ in range(3)]), + ( + "base_element_rosette_selection_method", + LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE_SUPERPOSED, + ), + ], + read_only=[ + ("id", "some_id"), + ("status", "UPTODATE"), + ], + ) From b1321ad82dcbfdc83657b8264c1fd18a847a74c1 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 7 Nov 2024 09:39:04 +0100 Subject: [PATCH 52/96] Harmonize enum and enum-parameter names (#659) Align the names of enum and the names of the respective parameters. Closes #658 --- doc/source/api/enum_types.rst | 12 ++--- examples/007_direction_definitions.py | 2 +- src/ansys/acp/core/__init__.py | 24 +++++----- src/ansys/acp/core/_tree_objects/__init__.py | 24 +++++----- .../_tree_objects/_grpc_helpers/mapping.py | 4 +- .../_tree_objects/boolean_selection_rule.py | 10 ++--- .../cylindrical_selection_rule.py | 18 ++++---- src/ansys/acp/core/_tree_objects/enums.py | 44 +++++++++---------- src/ansys/acp/core/_tree_objects/fabric.py | 18 ++++---- .../geometrical_selection_rule.py | 10 ++--- .../_tree_objects/imported_modeling_ply.py | 8 ++-- .../acp/core/_tree_objects/modeling_ply.py | 8 ++-- .../_tree_objects/oriented_selection_set.py | 8 ++-- .../_tree_objects/parallel_selection_rule.py | 18 ++++---- .../acp/core/_tree_objects/solid_model.py | 14 +++--- .../_tree_objects/spherical_selection_rule.py | 18 ++++---- src/ansys/acp/core/_tree_objects/stackup.py | 18 ++++---- .../core/_tree_objects/tube_selection_rule.py | 10 ++--- .../variable_offset_selection_rule.py | 10 ++--- .../unittests/test_boolean_selection_rule.py | 4 +- .../test_cylindrical_selection_rule.py | 8 ++-- tests/unittests/test_fabric.py | 30 ++++++------- .../test_geometrical_selection_rule.py | 4 +- tests/unittests/test_imported_modeling_ply.py | 4 +- tests/unittests/test_modeling_ply.py | 2 +- .../unittests/test_parallel_selection_rule.py | 8 ++-- tests/unittests/test_solid_model.py | 4 +- .../test_spherical_selection_rule.py | 8 ++-- tests/unittests/test_stackup.py | 18 ++++---- tests/unittests/test_tube_selection_rule.py | 4 +- .../test_variable_offset_selection_rule.py | 4 +- 31 files changed, 180 insertions(+), 196 deletions(-) diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 23125ba507..9ebc074a24 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -11,18 +11,18 @@ Enumeration data types BaseElementMaterialHandling BooleanOperationType CutOffGeometryOrientationType - CutoffMaterialType + CutoffMaterialHandling CutoffRuleType DimensionType - DrapingMaterialType + DrapingMaterialModel DrapingType - DropoffMaterialType + DropoffMaterialHandling DropOffType EdgeSetType ElementalDataType ElementTechnology ExtrusionGuideType - ExtrusionMethodType + ExtrusionMethod ExtrusionType FeFormat GeometricalRuleType @@ -37,7 +37,7 @@ Enumeration data types LookUpTableColumnValueType MeshImportType NodalDataType - OffsetDirectionType + SolidModelOffsetDirectionType OffsetType SnapToGeometryOrientationType PlyCutoffType @@ -51,7 +51,7 @@ Enumeration data types SolidModelExportFormat SolidModelImportFormat SolidModelSkinExportFormat - StatusType + Status StressStateType SymmetryType ThicknessFieldType diff --git a/examples/007_direction_definitions.py b/examples/007_direction_definitions.py index 45947cac65..f4a0084525 100644 --- a/examples/007_direction_definitions.py +++ b/examples/007_direction_definitions.py @@ -208,7 +208,7 @@ ply_angle=0, ply_material=fabric, oriented_selection_sets=[oss], - draping=DrapingType.TABULAR_VALUES, + draping_type=DrapingType.TABULAR_VALUES, draping_angle_1_field=angle_column_1, draping_angle_2_field=angle_column_2, ) diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 54c33f677d..4e5f2e1605 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -54,7 +54,7 @@ CADGeometry, CutOffGeometry, CutOffGeometryOrientationType, - CutoffMaterialType, + CutoffMaterialHandling, CutoffRuleType, CutoffSelectionRule, CutoffSelectionRuleElementalData, @@ -63,9 +63,9 @@ CylindricalSelectionRuleElementalData, CylindricalSelectionRuleNodalData, DimensionType, - DrapingMaterialType, + DrapingMaterialModel, DrapingType, - DropoffMaterialType, + DropoffMaterialHandling, DropOffSettings, DropOffType, EdgeSet, @@ -77,7 +77,7 @@ ElementTechnology, ExtrusionGuide, ExtrusionGuideType, - ExtrusionMethodType, + ExtrusionMethod, ExtrusionType, Fabric, FabricWithAngle, @@ -122,7 +122,6 @@ ModelingPlyNodalData, ModelNodalData, NodalDataType, - OffsetDirectionType, OffsetType, OrientedSelectionSet, OrientedSelectionSetElementalData, @@ -158,12 +157,13 @@ SolidModelExportSettings, SolidModelImportFormat, SolidModelNodalData, + SolidModelOffsetDirectionType, SolidModelSkinExportFormat, SphericalSelectionRule, SphericalSelectionRuleElementalData, SphericalSelectionRuleNodalData, Stackup, - StatusType, + Status, StressStateType, SubLaminate, SubShape, @@ -207,7 +207,7 @@ "ConnectLaunchConfig", "CutOffGeometry", "CutOffGeometryOrientationType", - "CutoffMaterialType", + "CutoffMaterialHandling", "CutoffRuleType", "CutoffSelectionRule", "CutoffSelectionRuleElementalData", @@ -218,9 +218,9 @@ "DimensionType", "DirectLaunchConfig", "DockerComposeLaunchConfig", - "DrapingMaterialType", + "DrapingMaterialModel", "DrapingType", - "DropoffMaterialType", + "DropoffMaterialHandling", "DropOffSettings", "DropOffType", "EdgeSet", @@ -233,7 +233,7 @@ "example_helpers", "ExtrusionGuide", "ExtrusionGuideType", - "ExtrusionMethodType", + "ExtrusionMethod", "ExtrusionType", "Fabric", "FabricWithAngle", @@ -286,7 +286,7 @@ "ModelingPlyNodalData", "ModelNodalData", "NodalDataType", - "OffsetDirectionType", + "SolidModelOffsetDirectionType", "OffsetType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", @@ -329,7 +329,7 @@ "SphericalSelectionRuleElementalData", "SphericalSelectionRuleNodalData", "Stackup", - "StatusType", + "Status", "StressStateType", "SubLaminate", "SubShape", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 34c560286a..cf8c088e22 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -48,18 +48,18 @@ BaseElementMaterialHandling, BooleanOperationType, CutOffGeometryOrientationType, - CutoffMaterialType, + CutoffMaterialHandling, CutoffRuleType, DimensionType, - DrapingMaterialType, + DrapingMaterialModel, DrapingType, - DropoffMaterialType, + DropoffMaterialHandling, DropOffType, EdgeSetType, ElementalDataType, ElementTechnology, ExtrusionGuideType, - ExtrusionMethodType, + ExtrusionMethod, ExtrusionType, GeometricalRuleType, ImportedPlyDrapingType, @@ -70,7 +70,6 @@ LookUpTableColumnValueType, MeshImportType, NodalDataType, - OffsetDirectionType, OffsetType, PlyCutoffType, PlyGeometryExportFormat, @@ -82,8 +81,9 @@ SensorType, SnapToGeometryOrientationType, SolidModelExportFormat, + SolidModelOffsetDirectionType, SolidModelSkinExportFormat, - StatusType, + Status, StressStateType, SymmetryType, ThicknessFieldType, @@ -183,7 +183,7 @@ "CADGeometry", "CutOffGeometry", "CutOffGeometryOrientationType", - "CutoffMaterialType", + "CutoffMaterialHandling", "CutoffRuleType", "CutoffSelectionRule", "CutoffSelectionRuleElementalData", @@ -192,9 +192,9 @@ "CylindricalSelectionRuleElementalData", "CylindricalSelectionRuleNodalData", "DimensionType", - "DrapingMaterialType", + "DrapingMaterialModel", "DrapingType", - "DropoffMaterialType", + "DropoffMaterialHandling", "DropOffSettings", "DropOffType", "EdgeSet", @@ -206,7 +206,7 @@ "ElementTechnology", "ExtrusionGuide", "ExtrusionGuideType", - "ExtrusionMethodType", + "ExtrusionMethod", "ExtrusionType", "Fabric", "FabricWithAngle", @@ -253,7 +253,7 @@ "ModelingPlyNodalData", "ModelNodalData", "NodalDataType", - "OffsetDirectionType", + "SolidModelOffsetDirectionType", "OffsetType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", @@ -295,7 +295,7 @@ "SphericalSelectionRuleElementalData", "SphericalSelectionRuleNodalData", "Stackup", - "StatusType", + "Status", "StressStateType", "SubLaminate", "SubShape", diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py index 57c571e154..35e8a8fafc 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/mapping.py @@ -36,7 +36,7 @@ from ..._utils.resource_paths import join as _rp_join from .._object_cache import ObjectCacheMixin, constructor_with_cache from ..base import CreatableTreeObject, ServerWrapper, TreeObject, TreeObjectBase -from ..enums import StatusType +from ..enums import Status from .exceptions import wrap_grpc_errors from .property_helper import _exposed_grpc_property, _wrap_doc from .protocols import EditableAndReadableResourceStub, ObjectInfo, ReadableResourceStub @@ -260,7 +260,7 @@ def get_read_only_collection_property( """Define a read-only mapping of child tree objects.""" def collection_property(self: ParentT) -> Mapping[ValueT]: - if requires_uptodate and hasattr(self, "status") and not self.status == StatusType.UPTODATE: + if requires_uptodate and hasattr(self, "status") and not self.status == Status.UPTODATE: raise RuntimeError( f"The object {self.name} must be up-to-date to access {object_class.__name__}." ) diff --git a/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py b/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py index d1f90f3edb..b1f30e01ff 100644 --- a/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py @@ -81,7 +81,7 @@ class BooleanSelectionRule(CreatableTreeObject, IdTreeObject): Name of the Boolean Selection Rule. selection_rules : - include_rule_type : + include_rule : Include or exclude area in rule. Setting this to ``False`` inverts the selection. """ @@ -98,11 +98,11 @@ def __init__( *, name: str = "BooleanSelectionrule", selection_rules: Iterable[LinkedSelectionRule] = (), - include_rule_type: bool = True, + include_rule: bool = True, ): super().__init__(name=name) self.selection_rules = selection_rules - self.include_rule_type = include_rule_type + self.include_rule = include_rule def _create_stub(self) -> boolean_selection_rule_pb2_grpc.ObjectServiceStub: return boolean_selection_rule_pb2_grpc.ObjectServiceStub(self._channel) @@ -118,9 +118,7 @@ def _create_stub(self) -> boolean_selection_rule_pb2_grpc.ObjectServiceStub: module_name=__module__, ) - include_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( - "properties.include_rule_type" - ) + include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") elemental_data = elemental_data_property(BooleanSelectionRuleElementalData) nodal_data = nodal_data_property(BooleanSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py b/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py index 68fbfdfc49..d0d23ad900 100644 --- a/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py @@ -91,9 +91,9 @@ class CylindricalSelectionRule(CreatableTreeObject, IdTreeObject): Direction of the Cylindrical Selection Rule. radius : Radius of the cylinder. - relative_rule_type : + relative_rule : If True, parameters are evaluated relative to size of the object. - include_rule_type : + include_rule : Include or exclude area in rule. Setting this to ``False`` inverts the selection. """ @@ -114,8 +114,8 @@ def __init__( origin: tuple[float, ...] = (0.0, 0.0, 0.0), direction: tuple[float, ...] = (0.0, 0.0, 1.0), radius: float = 0.0, - relative_rule_type: bool = False, - include_rule_type: bool = True, + relative_rule: bool = False, + include_rule: bool = True, ): super().__init__(name=name) self.use_global_coordinate_system = use_global_coordinate_system @@ -123,8 +123,8 @@ def __init__( self.origin = origin self.direction = direction self.radius = radius - self.relative_rule_type = relative_rule_type - self.include_rule_type = include_rule_type + self.relative_rule = relative_rule + self.include_rule = include_rule def _create_stub(self) -> cylindrical_selection_rule_pb2_grpc.ObjectServiceStub: return cylindrical_selection_rule_pb2_grpc.ObjectServiceStub(self._channel) @@ -142,12 +142,10 @@ def _create_stub(self) -> cylindrical_selection_rule_pb2_grpc.ObjectServiceStub: "properties.direction", from_protobuf=to_tuple_from_1D_array, to_protobuf=to_1D_double_array ) radius: ReadWriteProperty[float, float] = grpc_data_property("properties.radius") - relative_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( + relative_rule: ReadWriteProperty[bool, bool] = grpc_data_property( "properties.relative_rule_type" ) - include_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( - "properties.include_rule_type" - ) + include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") elemental_data = elemental_data_property(CylindricalSelectionRuleElementalData) nodal_data = nodal_data_property(CylindricalSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index 76456e3b80..d4715cdd91 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -51,12 +51,12 @@ "ArrowType", "BooleanOperationType", "CutOffGeometryOrientationType", - "CutoffMaterialType", + "CutoffMaterialHandling", "CutoffRuleType", "DimensionType", - "DrapingMaterialType", + "DrapingMaterialModel", "DrapingType", - "DropoffMaterialType", + "DropoffMaterialHandling", "DropOffType", "EdgeSetType", "ElementalDataType", @@ -65,7 +65,7 @@ "BaseElementMaterialHandling", "StressStateType", "ExtrusionGuideType", - "ExtrusionMethodType", + "ExtrusionMethod", "ExtrusionType", "GeometricalRuleType", "ImportedPlyDrapingType", @@ -75,7 +75,7 @@ "LookUpTable3DInterpolationAlgorithm", "LookUpTableColumnValueType", "NodalDataType", - "OffsetDirectionType", + "SolidModelOffsetDirectionType", "OffsetType", "PlyCutoffType", "PlyGeometryExportFormat", @@ -87,7 +87,7 @@ "SnapToGeometryOrientationType", "SolidModelExportFormat", "SolidModelSkinExportFormat", - "StatusType", + "Status", "SymmetryType", "ThicknessFieldType", "ThicknessType", @@ -95,8 +95,8 @@ "VirtualGeometryDimension", ] -(StatusType, status_type_to_pb, status_type_from_pb) = wrap_to_string_enum( - "StatusType", +(Status, status_type_to_pb, status_type_from_pb) = wrap_to_string_enum( + "Status", enum_types_pb2.StatusType, module=__name__, value_converter=lambda val: val, @@ -114,22 +114,22 @@ ) ( - CutoffMaterialType, + CutoffMaterialHandling, cut_off_material_type_to_pb, cut_off_material_type_from_pb, ) = wrap_to_string_enum( - "CutoffMaterialType", + "CutoffMaterialHandling", cut_off_material_pb2.MaterialHandlingType, module=__name__, doc="Options for how cut-off material is selected.", ) ( - DropoffMaterialType, + DropoffMaterialHandling, drop_off_material_type_to_pb, drop_off_material_type_from_pb, ) = wrap_to_string_enum( - "DropoffMaterialType", + "DropoffMaterialHandling", drop_off_material_pb2.MaterialHandlingType, module=__name__, doc="Options for how drop-off material is selected.", @@ -162,11 +162,11 @@ ) ( - DrapingMaterialType, + DrapingMaterialModel, draping_material_type_to_pb, draping_material_type_from_pb, ) = wrap_to_string_enum( - "DrapingMaterialType", + "DrapingMaterialModel", ply_material_pb2.DrapingMaterialType, module=__name__, doc="Options for the material type used in the draping algorithm.", @@ -472,13 +472,11 @@ doc="Determines how the intersection is computed for wireframe section cuts.", ) -(ExtrusionMethodType, extrusion_method_type_to_pb, extrusion_method_type_from_pb) = ( - wrap_to_string_enum( - "ExtrusionMethodType", - solid_model_pb2.ExtrusionMethodType, - module=__name__, - doc="Extrusion method used in a solid model.", - ) +(ExtrusionMethod, extrusion_method_type_to_pb, extrusion_method_type_from_pb) = wrap_to_string_enum( + "ExtrusionMethod", + solid_model_pb2.ExtrusionMethodType, + module=__name__, + doc="Extrusion method used in a solid model.", ) (ExtrusionGuideType, extrusion_guide_type_to_pb, extrusion_guide_type_from_pb) = ( @@ -490,9 +488,9 @@ ) ) -(OffsetDirectionType, offset_direction_type_to_pb, offset_direction_type_from_pb) = ( +(SolidModelOffsetDirectionType, offset_direction_type_to_pb, offset_direction_type_from_pb) = ( wrap_to_string_enum( - "OffsetDirectionType", + "SolidModelOffsetDirectionType", solid_model_pb2.OffsetDirectionType, module=__name__, doc=( diff --git a/src/ansys/acp/core/_tree_objects/fabric.py b/src/ansys/acp/core/_tree_objects/fabric.py index 99e04b8791..4ff1fc2f0c 100644 --- a/src/ansys/acp/core/_tree_objects/fabric.py +++ b/src/ansys/acp/core/_tree_objects/fabric.py @@ -35,9 +35,9 @@ ) from .base import CreatableTreeObject, IdTreeObject from .enums import ( - CutoffMaterialType, - DrapingMaterialType, - DropoffMaterialType, + CutoffMaterialHandling, + DrapingMaterialModel, + DropoffMaterialHandling, cut_off_material_type_from_pb, cut_off_material_type_to_pb, draping_material_type_from_pb, @@ -100,11 +100,11 @@ def __init__( thickness: float = 0.0, area_price: float = 0.0, ignore_for_postprocessing: bool = False, - drop_off_material_handling: DropoffMaterialType = "global", + drop_off_material_handling: DropoffMaterialHandling = "global", drop_off_material: Material | None = None, - cut_off_material_handling: CutoffMaterialType = "computed", + cut_off_material_handling: CutoffMaterialHandling = "computed", cut_off_material: Material | None = None, - draping_material_model: DrapingMaterialType = "woven", + draping_material_model: DrapingMaterialModel = "woven", draping_ud_coefficient: float = 0.0, ): super().__init__(name=name) @@ -113,11 +113,11 @@ def __init__( self.thickness = thickness self.area_price = area_price self.ignore_for_postprocessing = ignore_for_postprocessing - self.drop_off_material_handling = DropoffMaterialType(drop_off_material_handling) + self.drop_off_material_handling = DropoffMaterialHandling(drop_off_material_handling) self.drop_off_material = drop_off_material - self.cut_off_material_handling = CutoffMaterialType(cut_off_material_handling) + self.cut_off_material_handling = CutoffMaterialHandling(cut_off_material_handling) self.cut_off_material = cut_off_material - self.draping_material_model = DrapingMaterialType(draping_material_model) + self.draping_material_model = DrapingMaterialModel(draping_material_model) self.draping_ud_coefficient = draping_ud_coefficient def _create_stub(self) -> fabric_pb2_grpc.ObjectServiceStub: diff --git a/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py b/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py index b73720484f..7710d81cae 100644 --- a/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py @@ -93,7 +93,7 @@ class GeometricalSelectionRule(CreatableTreeObject, IdTreeObject): Virtual geometry to use for the rule. element_sets : Element sets to use for the rule. - include_rule_type : + include_rule : Include or exclude area in rule. Setting this to ``False`` inverts the selection. use_default_tolerances : @@ -120,7 +120,7 @@ def __init__( geometrical_rule_type: GeometricalRuleType = GeometricalRuleType.GEOMETRY, geometry: VirtualGeometry | None = None, element_sets: Iterable[ElementSet] = (), - include_rule_type: bool = True, + include_rule: bool = True, use_default_tolerances: bool = True, in_plane_capture_tolerance: float = 0.0, negative_capture_tolerance: float = 0.0, @@ -130,7 +130,7 @@ def __init__( self.geometrical_rule_type = geometrical_rule_type self.geometry = geometry self.element_sets = element_sets - self.include_rule_type = include_rule_type + self.include_rule = include_rule self.use_default_tolerances = use_default_tolerances self.in_plane_capture_tolerance = in_plane_capture_tolerance self.negative_capture_tolerance = negative_capture_tolerance @@ -148,9 +148,7 @@ def _create_stub(self) -> geometrical_selection_rule_pb2_grpc.ObjectServiceStub: ) geometry = grpc_link_property("properties.geometry", allowed_types=VirtualGeometry) element_sets = define_linked_object_list("properties.element_sets", object_class=ElementSet) - include_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( - "properties.include_rule_type" - ) + include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") use_default_tolerances: ReadWriteProperty[bool, bool] = grpc_data_property( "properties.use_default_tolerances" ) diff --git a/src/ansys/acp/core/_tree_objects/imported_modeling_ply.py b/src/ansys/acp/core/_tree_objects/imported_modeling_ply.py index 03a06e93ec..8614325c10 100644 --- a/src/ansys/acp/core/_tree_objects/imported_modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/imported_modeling_ply.py @@ -105,7 +105,7 @@ class ImportedModelingPly(CreatableTreeObject, IdTreeObject): The material (only fabric is supported) of the ply. ply_angle : Design angle between the reference direction and the ply fiber direction. - draping : + draping_type : Chooses between different draping formulations. ``NO_DRAPING`` by default. draping_angle_1_field : Correction angle between the fiber and draped fiber directions, in degree. @@ -148,7 +148,7 @@ def __init__( rotation_angle: float = 0.0, ply_material: Fabric | None = None, ply_angle: float = 0.0, - draping: ImportedPlyDrapingType = ImportedPlyDrapingType.NO_DRAPING, + draping_type: ImportedPlyDrapingType = ImportedPlyDrapingType.NO_DRAPING, draping_angle_1_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, draping_angle_2_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, thickness_type: ImportedPlyThicknessType = ImportedPlyThicknessType.NOMINAL, @@ -169,7 +169,7 @@ def __init__( self.ply_material = ply_material self.ply_angle = ply_angle - self.draping = ImportedPlyDrapingType(draping) + self.draping_type = ImportedPlyDrapingType(draping_type) self.draping_angle_1_field = draping_angle_1_field self.draping_angle_2_field = draping_angle_2_field @@ -210,7 +210,7 @@ def _create_stub(self) -> imported_modeling_ply_pb2_grpc.ObjectServiceStub: ply_material = grpc_link_property("properties.ply_material", allowed_types=(Fabric,)) ply_angle: ReadWriteProperty[float, float] = grpc_data_property("properties.ply_angle") - draping = grpc_data_property( + draping_type = grpc_data_property( "properties.draping", from_protobuf=imported_ply_draping_type_from_pb, to_protobuf=imported_ply_draping_type_to_pb, diff --git a/src/ansys/acp/core/_tree_objects/modeling_ply.py b/src/ansys/acp/core/_tree_objects/modeling_ply.py index 0dd1001bab..abc7eaaed2 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/modeling_ply.py @@ -255,7 +255,7 @@ class ModelingPly(CreatableTreeObject, IdTreeObject): Defines the global ply order. selection_rules : Selection Rules which may limit the extent of the ply. - draping : + draping_type : Chooses between different draping formulations. draping_seed_point : Starting point of the draping algorithm. @@ -319,7 +319,7 @@ def __init__( # if global_ply_nr == 0 global_ply_nr: int = 0, selection_rules: Iterable[LinkedSelectionRule] = (), - draping: DrapingType = DrapingType.NO_DRAPING, + draping_type: DrapingType = DrapingType.NO_DRAPING, draping_seed_point: tuple[float, float, float] = (0.0, 0.0, 0.0), auto_draping_direction: bool = True, draping_direction: tuple[float, float, float] = (1.0, 0.0, 0.0), @@ -343,7 +343,7 @@ def __init__( self.active = active self.global_ply_nr = global_ply_nr self.selection_rules = selection_rules - self.draping = draping + self.draping_type = draping_type self.draping_seed_point = draping_seed_point self.auto_draping_direction = auto_draping_direction self.draping_direction = draping_direction @@ -378,7 +378,7 @@ def _create_stub(self) -> modeling_ply_pb2_grpc.ObjectServiceStub: active: ReadWriteProperty[bool, bool] = grpc_data_property("properties.active") global_ply_nr: ReadWriteProperty[int, int] = grpc_data_property("properties.global_ply_nr") - draping = grpc_data_property( + draping_type = grpc_data_property( "properties.draping", from_protobuf=draping_type_from_pb, to_protobuf=draping_type_to_pb ) draping_seed_point = grpc_data_property( diff --git a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py index bd0735146e..28994688da 100644 --- a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py +++ b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py @@ -52,7 +52,7 @@ from .cylindrical_selection_rule import CylindricalSelectionRule from .element_set import ElementSet from .enums import ( - DrapingMaterialType, + DrapingMaterialModel, RosetteSelectionMethod, draping_material_type_from_pb, draping_material_type_to_pb, @@ -149,7 +149,7 @@ class OrientedSelectionSet(CreatableTreeObject, IdTreeObject): draping_ud_coefficient : Value between ``0`` and ``1`` which determines the amount of deformation in the transverse direction if the draping material model is set to - :attr:`DrapingMaterialType.UD`. + :attr:`DrapingMaterialModel.UD`. rotation_angle : Angle in degrees by which the initial reference directions are rotated around the orientations. reference_direction_field : @@ -180,7 +180,7 @@ def __init__( draping_direction: tuple[float, float, float] = (0.0, 0.0, 1.0), use_default_draping_mesh_size: bool = True, draping_mesh_size: float = 0.0, - draping_material_model: DrapingMaterialType = DrapingMaterialType.WOVEN, + draping_material_model: DrapingMaterialModel = DrapingMaterialModel.WOVEN, draping_ud_coefficient: float = 0.0, rotation_angle: float = 0.0, reference_direction_field: LookUpTable1DColumn | LookUpTable3DColumn | None = None, @@ -198,7 +198,7 @@ def __init__( self.draping_direction = draping_direction self.use_default_draping_mesh_size = use_default_draping_mesh_size self.draping_mesh_size = draping_mesh_size - self.draping_material_model = DrapingMaterialType(draping_material_model) + self.draping_material_model = DrapingMaterialModel(draping_material_model) self.draping_ud_coefficient = draping_ud_coefficient self.rotation_angle = rotation_angle self.reference_direction_field = reference_direction_field diff --git a/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py b/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py index c9bcfac5e4..656031bc6f 100644 --- a/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py @@ -93,9 +93,9 @@ class ParallelSelectionRule(CreatableTreeObject, IdTreeObject): Negative distance of the Parallel Selection Rule. upper_limit : Positive distance of the Parallel Selection Rule. - relative_rule_type : + relative_rule : If True, parameters are evaluated relative to size of the object. - include_rule_type : + include_rule : Include or exclude area in rule. Setting this to ``False`` inverts the selection. """ @@ -117,8 +117,8 @@ def __init__( direction: tuple[float, ...] = (1.0, 0.0, 0.0), lower_limit: float = 0.0, upper_limit: float = 0.0, - relative_rule_type: bool = False, - include_rule_type: bool = True, + relative_rule: bool = False, + include_rule: bool = True, ): super().__init__(name=name) self.use_global_coordinate_system = use_global_coordinate_system @@ -127,8 +127,8 @@ def __init__( self.direction = direction self.lower_limit = lower_limit self.upper_limit = upper_limit - self.relative_rule_type = relative_rule_type - self.include_rule_type = include_rule_type + self.relative_rule = relative_rule + self.include_rule = include_rule def _create_stub(self) -> parallel_selection_rule_pb2_grpc.ObjectServiceStub: return parallel_selection_rule_pb2_grpc.ObjectServiceStub(self._channel) @@ -147,12 +147,10 @@ def _create_stub(self) -> parallel_selection_rule_pb2_grpc.ObjectServiceStub: ) lower_limit: ReadWriteProperty[float, float] = grpc_data_property("properties.lower_limit") upper_limit: ReadWriteProperty[float, float] = grpc_data_property("properties.upper_limit") - relative_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( + relative_rule: ReadWriteProperty[bool, bool] = grpc_data_property( "properties.relative_rule_type" ) - include_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( - "properties.include_rule_type" - ) + include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") elemental_data = elemental_data_property(ParallelSelectionRuleElementalData) nodal_data = nodal_data_property(ParallelSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index 96ccb685a2..72fc4eb8c6 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -70,8 +70,8 @@ from .element_set import ElementSet from .enums import ( DropOffType, - ExtrusionMethodType, - OffsetDirectionType, + ExtrusionMethod, + SolidModelOffsetDirectionType, drop_off_type_from_pb, drop_off_type_to_pb, extrusion_method_type_from_pb, @@ -371,7 +371,7 @@ class SolidModel(SolidModelExportMixin, CreatableTreeObject, IdTreeObject): ply_group_pointers : Explicitly defines modeling plies where a new element is introduced. Only used if the ``extrusion_method`` is ``USER_DEFINED``. - offset_direction : + offset_direction_type : Determines how the extrusion direction is defined. With ``SHELL_NORMAL``, the normal direction of the shell is used during the entire extrusion. With ``SURFACE_NORMAL``, the offset direction is re-evaluated based @@ -418,10 +418,10 @@ def __init__( name: str = "SolidModel", active: bool = True, element_sets: Iterable[ElementSet | OrientedSelectionSet] = (), - extrusion_method: ExtrusionMethodType = ExtrusionMethodType.ANALYSIS_PLY_WISE, + extrusion_method: ExtrusionMethod = ExtrusionMethod.ANALYSIS_PLY_WISE, max_element_thickness: float = 1.0, ply_group_pointers: Iterable[ModelingPly] = (), - offset_direction: OffsetDirectionType = OffsetDirectionType.SHELL_NORMAL, + offset_direction_type: SolidModelOffsetDirectionType = SolidModelOffsetDirectionType.SHELL_NORMAL, skip_elements_without_plies: bool = False, drop_off_material: Material | None = None, cut_off_material: Material | None = None, @@ -439,7 +439,7 @@ def __init__( self.extrusion_method = extrusion_method self.max_element_thickness = max_element_thickness self.ply_group_pointers = ply_group_pointers - self.offset_direction = offset_direction + self.offset_direction_type = offset_direction_type self.skip_elements_without_plies = skip_elements_without_plies self.drop_off_material = drop_off_material self.cut_off_material = cut_off_material @@ -468,7 +468,7 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: "properties.max_element_thickness" ) ply_group_pointers = define_linked_object_list("properties.ply_group_pointers", ModelingPly) - offset_direction = grpc_data_property( + offset_direction_type = grpc_data_property( "properties.offset_direction", from_protobuf=offset_direction_type_from_pb, to_protobuf=offset_direction_type_to_pb, diff --git a/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py b/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py index 7fbb45702b..3a78b22e11 100644 --- a/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py @@ -89,9 +89,9 @@ class SphericalSelectionRule(CreatableTreeObject, IdTreeObject): Origin of the sphere. radius : Radius of the sphere. - relative_rule_type : + relative_rule : If True, parameters are evaluated relative to size of the object. - include_rule_type : + include_rule : Include or exclude area in rule. Setting this to ``False`` inverts the selection. """ @@ -111,16 +111,16 @@ def __init__( rosette: Rosette | None = None, origin: tuple[float, ...] = (0.0, 0.0, 0.0), radius: float = 0.0, - relative_rule_type: bool = False, - include_rule_type: bool = True, + relative_rule: bool = False, + include_rule: bool = True, ): super().__init__(name=name) self.use_global_coordinate_system = use_global_coordinate_system self.rosette = rosette self.origin = origin self.radius = radius - self.relative_rule_type = relative_rule_type - self.include_rule_type = include_rule_type + self.relative_rule = relative_rule + self.include_rule = include_rule def _create_stub(self) -> spherical_selection_rule_pb2_grpc.ObjectServiceStub: return spherical_selection_rule_pb2_grpc.ObjectServiceStub(self._channel) @@ -138,12 +138,10 @@ def _create_stub(self) -> spherical_selection_rule_pb2_grpc.ObjectServiceStub: "properties.direction", from_protobuf=to_tuple_from_1D_array, to_protobuf=to_1D_double_array ) radius: ReadWriteProperty[float, float] = grpc_data_property("properties.radius") - relative_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( + relative_rule: ReadWriteProperty[bool, bool] = grpc_data_property( "properties.relative_rule_type" ) - include_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( - "properties.include_rule_type" - ) + include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") elemental_data = elemental_data_property(SphericalSelectionRuleElementalData) nodal_data = nodal_data_property(SphericalSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/stackup.py b/src/ansys/acp/core/_tree_objects/stackup.py index 8f69713553..aeb33a82f9 100644 --- a/src/ansys/acp/core/_tree_objects/stackup.py +++ b/src/ansys/acp/core/_tree_objects/stackup.py @@ -44,9 +44,9 @@ ) from .base import CreatableTreeObject, IdTreeObject from .enums import ( - CutoffMaterialType, - DrapingMaterialType, - DropoffMaterialType, + CutoffMaterialHandling, + DrapingMaterialModel, + DropoffMaterialHandling, SymmetryType, cut_off_material_type_from_pb, cut_off_material_type_to_pb, @@ -197,11 +197,11 @@ def __init__( topdown: bool = True, fabrics: Sequence[FabricWithAngle] = tuple(), area_price: float = 0.0, - drop_off_material_handling: DropoffMaterialType = "global", + drop_off_material_handling: DropoffMaterialHandling = "global", drop_off_material: Material | None = None, cut_off_material: Material | None = None, - cut_off_material_handling: CutoffMaterialType = "computed", - draping_material_model: DrapingMaterialType = "woven", + cut_off_material_handling: CutoffMaterialHandling = "computed", + draping_material_model: DrapingMaterialModel = "woven", draping_ud_coefficient: float = 0.0, ): super().__init__(name=name) @@ -210,11 +210,11 @@ def __init__( self.topdown = topdown self.area_price = area_price self.fabrics = fabrics - self.drop_off_material_handling = DropoffMaterialType(drop_off_material_handling) + self.drop_off_material_handling = DropoffMaterialHandling(drop_off_material_handling) self.drop_off_material = drop_off_material - self.cut_off_material_handling = CutoffMaterialType(cut_off_material_handling) + self.cut_off_material_handling = CutoffMaterialHandling(cut_off_material_handling) self.cut_off_material = cut_off_material - self.draping_material_model = DrapingMaterialType(draping_material_model) + self.draping_material_model = DrapingMaterialModel(draping_material_model) self.draping_ud_coefficient = draping_ud_coefficient def _create_stub(self) -> stackup_pb2_grpc.ObjectServiceStub: diff --git a/src/ansys/acp/core/_tree_objects/tube_selection_rule.py b/src/ansys/acp/core/_tree_objects/tube_selection_rule.py index 6c3f29c63b..17f90ac91d 100644 --- a/src/ansys/acp/core/_tree_objects/tube_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/tube_selection_rule.py @@ -86,7 +86,7 @@ class TubeSelectionRule(CreatableTreeObject, IdTreeObject): Outer radius of the tube. inner_radius : Inner radius of the tube. - include_rule_type : + include_rule : Include or exclude area in rule. Setting this to ``False`` inverts the selection. extend_endings : @@ -118,7 +118,7 @@ def __init__( edge_set: EdgeSet | None = None, outer_radius: float = 1.0, inner_radius: float = 0.0, - include_rule_type: bool = True, + include_rule: bool = True, extend_endings: bool = False, symmetrical_extension: bool = True, head: tuple[float, float, float] = (0.0, 0.0, 0.0), @@ -129,7 +129,7 @@ def __init__( self.edge_set = edge_set self.outer_radius = outer_radius self.inner_radius = inner_radius - self.include_rule_type = include_rule_type + self.include_rule = include_rule self.extend_endings = extend_endings self.symmetrical_extension = symmetrical_extension self.head = head @@ -144,9 +144,7 @@ def _create_stub(self) -> tube_selection_rule_pb2_grpc.ObjectServiceStub: edge_set = grpc_link_property("properties.edge_set", allowed_types=EdgeSet) outer_radius: ReadWriteProperty[float, float] = grpc_data_property("properties.outer_radius") inner_radius: ReadWriteProperty[float, float] = grpc_data_property("properties.inner_radius") - include_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( - "properties.include_rule_type" - ) + include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") extend_endings: ReadWriteProperty[bool, bool] = grpc_data_property("properties.extend_endings") symmetrical_extension: ReadWriteProperty[bool, bool] = grpc_data_property( "properties.symmetrical_extension" diff --git a/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py b/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py index b70c8934ac..3830cb8c9f 100644 --- a/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py @@ -91,7 +91,7 @@ class VariableOffsetSelectionRule(CreatableTreeObject, IdTreeObject): Defines the in-plane offset. Cuts elements which are closer to the edge than this value. angles : Defines the angle between the reference surface and the cutting plane. - include_rule_type : + include_rule : Include or exclude area in rule. Setting this to ``False`` inverts the selection. use_offset_correction : @@ -125,7 +125,7 @@ def __init__( edge_set: EdgeSet | None = None, offsets: LookUpTable1DColumn | None = None, angles: LookUpTable1DColumn | None = None, - include_rule_type: bool = True, + include_rule: bool = True, use_offset_correction: bool = False, element_set: ElementSet | None = None, inherit_from_lookup_table: bool = True, @@ -137,7 +137,7 @@ def __init__( self.edge_set = edge_set self.offsets = offsets self.angles = angles - self.include_rule_type = include_rule_type + self.include_rule = include_rule self.use_offset_correction = use_offset_correction self.element_set = element_set self.inherit_from_lookup_table = inherit_from_lookup_table @@ -153,9 +153,7 @@ def _create_stub(self) -> variable_offset_selection_rule_pb2_grpc.ObjectServiceS edge_set = grpc_link_property("properties.edge_set", allowed_types=EdgeSet) offsets = grpc_link_property("properties.offsets", allowed_types=LookUpTable1DColumn) angles = grpc_link_property("properties.angles", allowed_types=LookUpTable1DColumn) - include_rule_type: ReadWriteProperty[bool, bool] = grpc_data_property( - "properties.include_rule_type" - ) + include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") use_offset_correction: ReadWriteProperty[bool, bool] = grpc_data_property( "properties.use_offset_correction" ) diff --git a/tests/unittests/test_boolean_selection_rule.py b/tests/unittests/test_boolean_selection_rule.py index b447e3b253..db49b6df3f 100644 --- a/tests/unittests/test_boolean_selection_rule.py +++ b/tests/unittests/test_boolean_selection_rule.py @@ -52,7 +52,7 @@ def default_properties(): return { "status": "NOTUPTODATE", "selection_rules": [], - "include_rule_type": True, + "include_rule": True, } CREATE_METHOD_NAME = "create_boolean_selection_rule" @@ -112,7 +112,7 @@ def object_properties(parent_object): ), ], ), - ("include_rule_type", False), + ("include_rule", False), ], read_only=[ ("id", "some_id"), diff --git a/tests/unittests/test_cylindrical_selection_rule.py b/tests/unittests/test_cylindrical_selection_rule.py index 7674d32c0a..c3c55a13cc 100644 --- a/tests/unittests/test_cylindrical_selection_rule.py +++ b/tests/unittests/test_cylindrical_selection_rule.py @@ -51,8 +51,8 @@ def default_properties(): "origin": (0.0, 0.0, 0.0), "direction": (0.0, 0.0, 1.0), "radius": 0.0, - "relative_rule_type": False, - "include_rule_type": True, + "relative_rule": False, + "include_rule": True, } CREATE_METHOD_NAME = "create_cylindrical_selection_rule" @@ -70,8 +70,8 @@ def object_properties(parent_object): ("origin", (1.0, 2.0, 3.0)), ("direction", (4.0, 5.0, 6.0)), ("radius", 7.0), - ("relative_rule_type", True), - ("include_rule_type", False), + ("relative_rule", True), + ("include_rule", False), ], read_only=[ ("id", "some_id"), diff --git a/tests/unittests/test_fabric.py b/tests/unittests/test_fabric.py index 1e53dd7bb7..70b251c591 100644 --- a/tests/unittests/test_fabric.py +++ b/tests/unittests/test_fabric.py @@ -23,7 +23,7 @@ from packaging.version import parse as parse_version import pytest -from ansys.acp.core import CutoffMaterialType, DrapingMaterialType, DropoffMaterialType +from ansys.acp.core import CutoffMaterialHandling, DrapingMaterialModel, DropoffMaterialHandling from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester @@ -51,9 +51,9 @@ def default_properties(acp_instance): "thickness": 0.0, "area_price": 0.0, "ignore_for_postprocessing": False, - "drop_off_material_handling": DropoffMaterialType.GLOBAL, - "cut_off_material_handling": CutoffMaterialType.COMPUTED, - "draping_material_model": DrapingMaterialType.WOVEN, + "drop_off_material_handling": DropoffMaterialHandling.GLOBAL, + "cut_off_material_handling": CutoffMaterialHandling.COMPUTED, + "draping_material_model": DrapingMaterialModel.WOVEN, "draping_ud_coefficient": 0.0, "material": None, } @@ -63,11 +63,11 @@ def default_properties(acp_instance): "thickness": 0.0, "area_price": 0.0, "ignore_for_postprocessing": False, - "drop_off_material_handling": DropoffMaterialType.GLOBAL, + "drop_off_material_handling": DropoffMaterialHandling.GLOBAL, "drop_off_material": None, - "cut_off_material_handling": CutoffMaterialType.COMPUTED, + "cut_off_material_handling": CutoffMaterialHandling.COMPUTED, "cut_off_material": None, - "draping_material_model": DrapingMaterialType.WOVEN, + "draping_material_model": DrapingMaterialModel.WOVEN, "draping_ud_coefficient": 0.0, "material": None, } @@ -88,9 +88,9 @@ def object_properties(parent_object, acp_instance): ("thickness", 1e-6), ("area_price", 5.98), ("ignore_for_postprocessing", True), - ("drop_off_material_handling", DropoffMaterialType.GLOBAL), - ("cut_off_material_handling", CutoffMaterialType.COMPUTED), - ("draping_material_model", DrapingMaterialType.UD), + ("drop_off_material_handling", DropoffMaterialHandling.GLOBAL), + ("cut_off_material_handling", CutoffMaterialHandling.COMPUTED), + ("draping_material_model", DrapingMaterialModel.UD), ("draping_ud_coefficient", 0.55), ("material", material), ("material", None), @@ -110,11 +110,11 @@ def object_properties(parent_object, acp_instance): ("thickness", 1e-6), ("area_price", 5.98), ("ignore_for_postprocessing", True), - ("drop_off_material_handling", DropoffMaterialType.CUSTOM), + ("drop_off_material_handling", DropoffMaterialHandling.CUSTOM), ("drop_off_material", drop_off_material), - ("cut_off_material_handling", CutoffMaterialType.CUSTOM), + ("cut_off_material_handling", CutoffMaterialHandling.CUSTOM), ("cut_off_material", cut_off_material), - ("draping_material_model", DrapingMaterialType.UD), + ("draping_material_model", DrapingMaterialModel.UD), ("draping_ud_coefficient", 0.55), ("material", material), ("material", None), @@ -131,8 +131,8 @@ def object_properties(parent_object, acp_instance): @pytest.mark.parametrize("material_type", ["cut_off_material", "drop_off_material"]) def test_solid_model_materials(parent_object, tree_object, acp_instance, material_type): """Check that solid model materials are supported since 25.1.""" - tree_object.cut_off_material_handling = CutoffMaterialType.CUSTOM - tree_object.drop_off_material_handling = DropoffMaterialType.CUSTOM + tree_object.cut_off_material_handling = CutoffMaterialHandling.CUSTOM + tree_object.drop_off_material_handling = DropoffMaterialHandling.CUSTOM if parse_version(acp_instance.server_version) < parse_version("25.1"): with pytest.raises(RuntimeError) as exc: setattr(tree_object, material_type, parent_object.create_material(name="Material")) diff --git a/tests/unittests/test_geometrical_selection_rule.py b/tests/unittests/test_geometrical_selection_rule.py index 77ffbd85d0..3a924dc867 100644 --- a/tests/unittests/test_geometrical_selection_rule.py +++ b/tests/unittests/test_geometrical_selection_rule.py @@ -53,7 +53,7 @@ def default_properties(): "geometrical_rule_type": GeometricalRuleType.GEOMETRY, "geometry": None, "element_sets": [], - "include_rule_type": True, + "include_rule": True, "use_default_tolerances": True, "in_plane_capture_tolerance": 0.0, "negative_capture_tolerance": 0.0, @@ -74,7 +74,7 @@ def object_properties(parent_object): ("geometrical_rule_type", GeometricalRuleType.ELEMENT_SETS), ("geometry", geometry), ("element_sets", element_sets), - ("include_rule_type", False), + ("include_rule", False), ("use_default_tolerances", False), ("in_plane_capture_tolerance", 1.2), ("negative_capture_tolerance", 2.3), diff --git a/tests/unittests/test_imported_modeling_ply.py b/tests/unittests/test_imported_modeling_ply.py index ca19bdf7e5..1ec31cf894 100644 --- a/tests/unittests/test_imported_modeling_ply.py +++ b/tests/unittests/test_imported_modeling_ply.py @@ -84,7 +84,7 @@ def default_properties(): "rotation_angle": 0.0, "ply_material": None, "ply_angle": 0.0, - "draping": ImportedPlyDrapingType.NO_DRAPING, + "draping_type": ImportedPlyDrapingType.NO_DRAPING, "draping_angle_1_field": None, "draping_angle_2_field": None, "thickness_type": ImportedPlyThicknessType.NOMINAL, @@ -115,7 +115,7 @@ def object_properties(request, minimal_complete_model): ("rotation_angle", 67.2), ("ply_material", ply_material), ("ply_angle", 34.5), - ("draping", ImportedPlyDrapingType.TABULAR_VALUES), + ("draping_type", ImportedPlyDrapingType.TABULAR_VALUES), ("draping_angle_1_field", column_1), ("draping_angle_2_field", column_2), ("thickness_type", ImportedPlyThicknessType.FROM_TABLE), diff --git a/tests/unittests/test_modeling_ply.py b/tests/unittests/test_modeling_ply.py index a5cc3b6ffd..7697995646 100644 --- a/tests/unittests/test_modeling_ply.py +++ b/tests/unittests/test_modeling_ply.py @@ -76,7 +76,7 @@ def default_properties(): "ply_angle": 0.0, "active": True, "global_ply_nr": AnyThing(), - "draping": DrapingType.NO_DRAPING, + "draping_type": DrapingType.NO_DRAPING, "draping_seed_point": (0.0, 0.0, 0.0), "auto_draping_direction": True, "draping_direction": (1.0, 0.0, 0.0), diff --git a/tests/unittests/test_parallel_selection_rule.py b/tests/unittests/test_parallel_selection_rule.py index 37845a34dc..62bd32da35 100644 --- a/tests/unittests/test_parallel_selection_rule.py +++ b/tests/unittests/test_parallel_selection_rule.py @@ -56,8 +56,8 @@ def default_properties(): "direction": (1.0, 0.0, 0.0), "lower_limit": 0.0, "upper_limit": 0.0, - "relative_rule_type": False, - "include_rule_type": True, + "relative_rule": False, + "include_rule": True, } CREATE_METHOD_NAME = "create_parallel_selection_rule" @@ -76,8 +76,8 @@ def object_properties(parent_object): ("direction", (4.0, 5.0, 6.0)), ("lower_limit", 7.0), ("upper_limit", 8.0), - ("relative_rule_type", True), - ("include_rule_type", False), + ("relative_rule", True), + ("include_rule", False), ], read_only=[ ("id", "some_id"), diff --git a/tests/unittests/test_solid_model.py b/tests/unittests/test_solid_model.py index 9bdc5e22b2..b07060988e 100644 --- a/tests/unittests/test_solid_model.py +++ b/tests/unittests/test_solid_model.py @@ -90,10 +90,10 @@ def object_properties(parent_object): "element_sets", [model.create_element_set(), model.create_oriented_selection_set()], ), - ("extrusion_method", pyacp.ExtrusionMethodType.MONOLITHIC), + ("extrusion_method", pyacp.ExtrusionMethod.MONOLITHIC), ("max_element_thickness", 12.3), ("ply_group_pointers", [modeling_group.create_modeling_ply() for _ in range(3)]), - ("offset_direction", pyacp.OffsetDirectionType.SURFACE_NORMAL), + ("offset_direction_type", pyacp.SolidModelOffsetDirectionType.SURFACE_NORMAL), ("skip_elements_without_plies", True), ("drop_off_material", model.create_material()), ("cut_off_material", model.create_material()), diff --git a/tests/unittests/test_spherical_selection_rule.py b/tests/unittests/test_spherical_selection_rule.py index fa8212d93b..fa71608fac 100644 --- a/tests/unittests/test_spherical_selection_rule.py +++ b/tests/unittests/test_spherical_selection_rule.py @@ -50,8 +50,8 @@ def default_properties(): "rosette": None, "origin": (0.0, 0.0, 0.0), "radius": 0.0, - "relative_rule_type": False, - "include_rule_type": True, + "relative_rule": False, + "include_rule": True, } CREATE_METHOD_NAME = "create_spherical_selection_rule" @@ -68,8 +68,8 @@ def object_properties(parent_object): ("rosette", rosette), ("origin", (1.0, 2.0, 3.0)), ("radius", 4.0), - ("relative_rule_type", True), - ("include_rule_type", False), + ("relative_rule", True), + ("include_rule", False), ], read_only=[ ("id", "some_id"), diff --git a/tests/unittests/test_stackup.py b/tests/unittests/test_stackup.py index 04fdfb32e2..eec4c452b0 100644 --- a/tests/unittests/test_stackup.py +++ b/tests/unittests/test_stackup.py @@ -23,9 +23,9 @@ import pytest from ansys.acp.core import ( - CutoffMaterialType, - DrapingMaterialType, - DropoffMaterialType, + CutoffMaterialHandling, + DrapingMaterialModel, + DropoffMaterialHandling, FabricWithAngle, SymmetryType, ) @@ -56,11 +56,11 @@ def default_properties(): "topdown": True, "fabrics": [], "symmetry": SymmetryType.NO_SYMMETRY, - "drop_off_material_handling": DropoffMaterialType.GLOBAL, + "drop_off_material_handling": DropoffMaterialHandling.GLOBAL, "drop_off_material": None, - "cut_off_material_handling": CutoffMaterialType.COMPUTED, + "cut_off_material_handling": CutoffMaterialHandling.COMPUTED, "cut_off_material": None, - "draping_material_model": DrapingMaterialType.WOVEN, + "draping_material_model": DrapingMaterialModel.WOVEN, "draping_ud_coefficient": 0.0, } @@ -86,11 +86,11 @@ def object_properties(parent_object): ], ), ("symmetry", SymmetryType.EVEN_SYMMETRY), - ("drop_off_material_handling", DropoffMaterialType.CUSTOM), + ("drop_off_material_handling", DropoffMaterialHandling.CUSTOM), ("drop_off_material", material), - ("cut_off_material_handling", CutoffMaterialType.CUSTOM), + ("cut_off_material_handling", CutoffMaterialHandling.CUSTOM), ("cut_off_material", material), - ("draping_material_model", DrapingMaterialType.UD), + ("draping_material_model", DrapingMaterialModel.UD), ("draping_ud_coefficient", 0.55), ], read_only=[ diff --git a/tests/unittests/test_tube_selection_rule.py b/tests/unittests/test_tube_selection_rule.py index 92f7f9a0a9..c18a7b20ab 100644 --- a/tests/unittests/test_tube_selection_rule.py +++ b/tests/unittests/test_tube_selection_rule.py @@ -49,7 +49,7 @@ def default_properties(): "edge_set": None, "outer_radius": 1.0, "inner_radius": 0.0, - "include_rule_type": True, + "include_rule": True, "extend_endings": False, "symmetrical_extension": True, "head": (0.0, 0.0, 0.0), @@ -70,7 +70,7 @@ def object_properties(parent_object): ("edge_set", edge_set), ("outer_radius", 1.3), ("inner_radius", 0.3), - ("include_rule_type", False), + ("include_rule", False), ("extend_endings", True), ("symmetrical_extension", False), ("head", (1.0, 2.0, 3.0)), diff --git a/tests/unittests/test_variable_offset_selection_rule.py b/tests/unittests/test_variable_offset_selection_rule.py index 7c8dce5473..7678ccb910 100644 --- a/tests/unittests/test_variable_offset_selection_rule.py +++ b/tests/unittests/test_variable_offset_selection_rule.py @@ -52,7 +52,7 @@ def default_properties(): "edge_set": None, "offsets": None, "angles": None, - "include_rule_type": True, + "include_rule": True, "use_offset_correction": False, "element_set": None, "inherit_from_lookup_table": True, @@ -78,7 +78,7 @@ def object_properties(parent_object): ("edge_set", edge_set), ("offsets", column_1), ("angles", column_2), - ("include_rule_type", False), + ("include_rule", False), ("use_offset_correction", True), ("element_set", element_set), ("inherit_from_lookup_table", False), From cd251ba2b014b67711a898be0dc01ce04eb3d913 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 7 Nov 2024 10:10:52 +0100 Subject: [PATCH 53/96] Fix import solid model export tests (#660) The export tests for imported solid models were incorrectly operating on solid models instead of imported solid models. --- tests/unittests/test_imported_solid_model.py | 118 ++++++++++--------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/tests/unittests/test_imported_solid_model.py b/tests/unittests/test_imported_solid_model.py index 985a217ee5..ac0e00a071 100644 --- a/tests/unittests/test_imported_solid_model.py +++ b/tests/unittests/test_imported_solid_model.py @@ -124,6 +124,27 @@ def object_properties(parent_object): ) +@pytest.fixture +def imported_solid_model_with_elements(parent_object, tempdir_if_local_acp): + model = parent_object + solid_model = model.create_solid_model() + solid_model.element_sets = [model.element_sets["All_Elements"]] + model.update() + with tempdir_if_local_acp() as export_dir: + filename = export_dir / "solid_model.h5" + solid_model.export(filename, format=pyacp.SolidModelExportFormat.ANSYS_H5) + del model.solid_models[solid_model.id] + imported_solid_model = model.create_imported_solid_model( + external_path=filename, + format=pyacp.SolidModelImportFormat.ANSYS_H5, + ) + imported_solid_model.create_layup_mapping_object( + shell_element_sets=[model.element_sets["All_Elements"]] + ) + model.update() + yield imported_solid_model + + @pytest.mark.parametrize( "format", [ @@ -133,93 +154,76 @@ def object_properties(parent_object): pyacp.SolidModelExportFormat.ANSYS_CDB, ], ) -def test_export(acp_instance, parent_object, format): +def test_export(tempdir_if_local_acp, acp_instance, imported_solid_model_with_elements, format): """Check that the export to a file works.""" - model = parent_object - solid_model = model.create_solid_model() - solid_model.element_sets = [model.element_sets["All_Elements"]] - model.update() + with tempdir_if_local_acp() as export_dir: + with tempfile.TemporaryDirectory() as tmp_dir: + if format == "ansys:h5": + ext = ".h5" + else: + ext = ".cdb" - with tempfile.TemporaryDirectory() as tmp_dir: - if format == "ansys:h5": - ext = ".h5" - else: - ext = ".cdb" + out_file_name = f"out_file{ext}" + out_path = export_dir / out_file_name + tmp_path = pathlib.Path(tmp_dir) / out_file_name - out_file_name = f"out_file{ext}" - out_path = pathlib.Path(tmp_dir) / out_file_name - - if not acp_instance.is_remote: - # save directly to the local file, to avoid a copy in the working directory - out_file_name = out_path # type: ignore + imported_solid_model_with_elements.export(path=out_file_name, format=format) + acp_instance.download_file(out_path, tmp_path) - solid_model.export(path=out_file_name, format=format) - acp_instance.download_file(out_file_name, out_path) - - assert out_path.exists() - assert out_path.stat().st_size > 0 + assert tmp_path.exists() + assert tmp_path.stat().st_size > 0 @given(invalid_format=st.text()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) -def test_export_with_invalid_format_raises(parent_object, invalid_format): +def test_export_with_invalid_format_raises( + imported_solid_model_with_elements, tempdir_if_local_acp, invalid_format +): """Check that the export to a file with an invalid format raises an exception.""" assume(invalid_format not in ["ansys:h5", "ansys:cdb"]) - model = parent_object - solid_model = model.create_solid_model() - solid_model.element_sets = [model.element_sets["All_Elements"]] - model.update() - - with tempfile.TemporaryDirectory() as tmp_dir: + with tempdir_if_local_acp() as export_dir: out_file_name = f"out_file.h5" - out_path = pathlib.Path(tmp_dir) / out_file_name + out_path = export_dir / out_file_name with pytest.raises(ValueError): - solid_model.export(path=out_path, format=invalid_format) + imported_solid_model_with_elements.export(path=out_path, format=invalid_format) @pytest.mark.parametrize("format", ["ansys:cdb", "step", "iges", "stl"]) -def test_skin_export(acp_instance, parent_object, format): +def test_skin_export( + tempdir_if_local_acp, acp_instance, imported_solid_model_with_elements, format +): """Check that the skin export to a file works.""" - model = parent_object - solid_model = model.create_solid_model() - solid_model.element_sets = [model.element_sets["All_Elements"]] - model.update() + with tempdir_if_local_acp() as export_dir: + with tempfile.TemporaryDirectory() as tmp_dir: + ext = {"ansys:cdb": ".cdb", "step": ".stp", "iges": ".igs", "stl": ".stl"}[format] - with tempfile.TemporaryDirectory() as tmp_dir: - ext = {"ansys:cdb": ".cdb", "step": ".stp", "iges": ".igs", "stl": ".stl"}[format] - out_file_name = f"out_file{ext}" - out_path = pathlib.Path(tmp_dir) / out_file_name - - if not acp_instance.is_remote: - # save directly to the local file, to avoid a copy in the working directory - out_file_name = out_path # type: ignore + out_file_name = f"out_file{ext}" + out_path = export_dir / out_file_name + tmp_path = pathlib.Path(tmp_dir) / out_file_name - solid_model.export_skin(path=out_file_name, format=format) - acp_instance.download_file(out_file_name, out_path) + imported_solid_model_with_elements.export_skin(path=out_file_name, format=format) + acp_instance.download_file(out_path, tmp_path) - assert out_path.exists() - assert out_path.stat().st_size > 0 + assert tmp_path.exists() + assert tmp_path.stat().st_size > 0 @given(invalid_format=st.text()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) -def test_skin_export_with_invalid_format_raises(parent_object, invalid_format): +def test_skin_export_with_invalid_format_raises( + imported_solid_model_with_elements, tempdir_if_local_acp, invalid_format +): """Check that the export to a file with an invalid format raises an exception.""" assume(invalid_format not in ["ansys:cdb", "step", "iges", "stl"]) - model = parent_object - solid_model = model.create_solid_model() - solid_model.element_sets = [model.element_sets["All_Elements"]] - model.update() - - with tempfile.TemporaryDirectory() as tmp_dir: - out_file_name = f"out_file.stp" - out_path = pathlib.Path(tmp_dir) / out_file_name + with tempdir_if_local_acp() as export_dir: + out_file_name = f"out_file.h5" + out_path = export_dir / out_file_name with pytest.raises(ValueError): - solid_model.export_skin(path=out_path, format=invalid_format) + imported_solid_model_with_elements.export_skin(path=out_path, format=invalid_format) def test_refresh(tempdir_if_local_acp, parent_object): From 0cbef897d560aa97266344dcfa26dea64362bd19 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 7 Nov 2024 10:48:41 +0100 Subject: [PATCH 54/96] Add HDF5 Composite CAE import and export methods (#646) Add methods for importing from / exporting to the HDF5 Composite CAE format. Nested messages in the API are defined as dataclasses. The alternative would be to expose them in a flat way, but I think the same reasoning for nesting them as in the API applies here: The nested structure is slightly easier to parse. Closes #598. --- .flake8 | 2 +- doc/source/api/enum_types.rst | 2 + doc/source/api/index.rst | 1 + doc/source/api/other_types.rst | 14 ++ doc/source/api/tree_objects.rst | 3 - src/ansys/acp/core/__init__.py | 11 ++ src/ansys/acp/core/_tree_objects/__init__.py | 19 +- src/ansys/acp/core/_tree_objects/model.py | 185 ++++++++++++++++++- src/ansys/acp/core/_tree_objects/utils.py | 46 +++++ tests/unittests/test_model.py | 81 ++++++++ 10 files changed, 356 insertions(+), 8 deletions(-) create mode 100644 doc/source/api/other_types.rst create mode 100644 src/ansys/acp/core/_tree_objects/utils.py diff --git a/.flake8 b/.flake8 index 4f0551e3f8..34c4ca7131 100644 --- a/.flake8 +++ b/.flake8 @@ -3,5 +3,5 @@ exclude = venv, __init__.py, doc/_build select = W191, W291, W293, W391, E115, E117, E122, E124, E125, E225, E231, E301, E303, E501, F401, F403 count = True max-complexity = 10 -max-line-length = 110 +max-line-length = 115 statistics = True diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 9ebc074a24..45f9808735 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -34,6 +34,8 @@ Enumeration data types LayupMappingRosetteSelectionMethod LinkedObjectHandling LookUpTable3DInterpolationAlgorithm + HDF5CompositeCAEImportMode + HDF5CompositeCAEProjectionMode LookUpTableColumnValueType MeshImportType NodalDataType diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index f4cab8f7a6..6d720e9b4c 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -15,6 +15,7 @@ and attributes. linked_object_definitions material_property_sets enum_types + other_types plot_utils other_utils workflow diff --git a/doc/source/api/other_types.rst b/doc/source/api/other_types.rst new file mode 100644 index 0000000000..b6cc687cef --- /dev/null +++ b/doc/source/api/other_types.rst @@ -0,0 +1,14 @@ +Other types +----------- + +.. currentmodule:: ansys.acp.core + +.. autosummary:: + :toctree: _autosummary + + CoordinateTransformation + ImportedSolidModelExportSettings + ShellMappingProperties + SolidMappingProperties + SolidModelExportSettings + DropOffSettings diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index ebf70cd405..170610f494 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -14,7 +14,6 @@ ACP objects CutOffGeometry CutoffSelectionRule CylindricalSelectionRule - DropOffSettings EdgeSet ElementSet ExtrusionGuide @@ -26,7 +25,6 @@ ACP objects ImportedModelingPly ImportedProductionPly ImportedSolidModel - ImportedSolidModelExportSettings InterfaceLayer LayupMappingObject LookUpTable1D @@ -47,7 +45,6 @@ ACP objects SnapToGeometry SolidElementSet SolidModel - SolidModelExportSettings SphericalSelectionRule Stackup SubLaminate diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 4e5f2e1605..340f719fdb 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -52,6 +52,7 @@ ButtJointSequence, CADComponent, CADGeometry, + CoordinateTransformation, CutOffGeometry, CutOffGeometryOrientationType, CutoffMaterialHandling, @@ -87,6 +88,8 @@ GeometricalSelectionRule, GeometricalSelectionRuleElementalData, GeometricalSelectionRuleNodalData, + HDF5CompositeCAEImportMode, + HDF5CompositeCAEProjectionMode, IgnorableEntity, ImportedAnalysisPly, ImportedModelingGroup, @@ -146,11 +149,13 @@ SectionCutType, Sensor, SensorType, + ShellMappingProperties, SnapToGeometry, SnapToGeometryOrientationType, SolidElementSet, SolidElementSetElementalData, SolidElementSetNodalData, + SolidMappingProperties, SolidModel, SolidModelElementalData, SolidModelExportFormat, @@ -205,6 +210,7 @@ "CADComponent", "CADGeometry", "ConnectLaunchConfig", + "CoordinateTransformation", "CutOffGeometry", "CutOffGeometryOrientationType", "CutoffMaterialHandling", @@ -231,6 +237,7 @@ "ElementSetNodalData", "ElementTechnology", "example_helpers", + "ExportSettings", "ExtrusionGuide", "ExtrusionGuideType", "ExtrusionMethod", @@ -247,6 +254,8 @@ "get_directions_plotter", "get_dpf_unit_system", "get_model_tree", + "HDF5CompositeCAEImportMode", + "HDF5CompositeCAEProjectionMode", "IgnorableEntity", "ImportedAnalysisPly", "ImportedModelingGroup", @@ -313,11 +322,13 @@ "SectionCutType", "Sensor", "SensorType", + "ShellMappingProperties", "SnapToGeometry", "SnapToGeometryOrientationType", "SolidElementSet", "SolidElementSetElementalData", "SolidElementSetNodalData", + "SolidMappingProperties", "SolidModel", "SolidModelElementalData", "SolidModelExportFormat", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index cf8c088e22..1cfaa187c3 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -118,7 +118,18 @@ from .lookup_table_3d import LookUpTable3D from .lookup_table_3d_column import LookUpTable3DColumn from .material import Material -from .model import FeFormat, IgnorableEntity, MeshData, Model, ModelElementalData, ModelNodalData +from .model import ( + FeFormat, + HDF5CompositeCAEImportMode, + HDF5CompositeCAEProjectionMode, + IgnorableEntity, + MeshData, + Model, + ModelElementalData, + ModelNodalData, + ShellMappingProperties, + SolidMappingProperties, +) from .modeling_group import ModelingGroup from .modeling_ply import ModelingPly, ModelingPlyElementalData, ModelingPlyNodalData, TaperEdge from .oriented_selection_set import ( @@ -161,6 +172,7 @@ TubeSelectionRuleElementalData, TubeSelectionRuleNodalData, ) +from .utils import CoordinateTransformation from .variable_offset_selection_rule import ( VariableOffsetSelectionRule, VariableOffsetSelectionRuleElementalData, @@ -181,6 +193,7 @@ "ButtJointSequence", "CADComponent", "CADGeometry", + "CoordinateTransformation", "CutOffGeometry", "CutOffGeometryOrientationType", "CutoffMaterialHandling", @@ -217,6 +230,8 @@ "GeometricalSelectionRule", "GeometricalSelectionRuleElementalData", "GeometricalSelectionRuleNodalData", + "HDF5CompositeCAEImportMode", + "HDF5CompositeCAEProjectionMode", "IgnorableEntity", "ImportedAnalysisPly", "ImportedModelingGroup", @@ -279,11 +294,13 @@ "SectionCutType", "Sensor", "SensorType", + "ShellMappingProperties", "SnapToGeometry", "SnapToGeometryOrientationType", "SolidElementSet", "SolidElementSetElementalData", "SolidElementSetNodalData", + "SolidMappingProperties", "SolidModel", "SolidModelElementalData", "SolidModelExportFormat", diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 56cebf03c4..ff3692e597 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -22,7 +22,7 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Iterable, Sequence import dataclasses import typing from typing import Any, cast @@ -137,16 +137,21 @@ from .stackup import Stackup from .sublaminate import SubLaminate from .tube_selection_rule import TubeSelectionRule +from .utils import CoordinateTransformation from .variable_offset_selection_rule import VariableOffsetSelectionRule from .virtual_geometry import VirtualGeometry __all__ = [ + "FeFormat", + "HDF5CompositeCAEImportMode", + "HDF5CompositeCAEProjectionMode", + "IgnorableEntity", "MeshData", "Model", "ModelElementalData", "ModelNodalData", - "FeFormat", - "IgnorableEntity", + "ShellMappingProperties", + "SolidMappingProperties", ] FeFormat, fe_format_to_pb, _ = wrap_to_string_enum( @@ -167,6 +172,18 @@ module=__name__, doc="Options for the entities to ignore when loading an FE file.", ) +HDF5CompositeCAEImportMode, hdf5_composite_cae_import_mode_to_pb, _ = wrap_to_string_enum( + "HDF5CompositeCAEImportMode", + model_pb2.ImportMode, + module=__name__, + doc="Options for the import mode of the HDF5 Composite CAE file.", +) +HDF5CompositeCAEProjectionMode, hdf5_composite_cae_projection_mode_to_pb, _ = wrap_to_string_enum( + "HDF5CompositeCAEProjectionMode", + model_pb2.ProjectionMode, + module=__name__, + doc="Options for the projection mode of the HDF5 Composite CAE file.", +) @dataclasses.dataclass @@ -213,6 +230,25 @@ class ModelNodalData(NodalData): """Represents nodal data for a Model.""" +@dataclasses.dataclass +class ShellMappingProperties: + """Properties for mapping to the shell on importing HDF5 Composite CAE files.""" + + all_elements: bool = True + element_sets: Sequence[ElementSet] = () + relative_thickness_tolerance: float = 0.5 + relative_in_plane_tolerance: float = 0.01 + angle_tolerance: float = 35.0 + small_hole_threshold: float = 0.0 + + +@dataclasses.dataclass +class SolidMappingProperties: + """Properties for importing HDF5 Composite CAE files as imported plies.""" + + offset_type: OffsetType = OffsetType.BOTTOM_OFFSET + + @mark_grpc_properties @register class Model(TreeObject): @@ -423,6 +459,149 @@ def export_analysis_model(self, path: _PATH) -> None: ) ) + @supported_since("25.1") + def export_hdf5_composite_cae( + self, + path: _PATH, + *, + remove_midside_nodes: bool = True, + layup_representation_3d: bool = False, + offset_type: OffsetType = OffsetType.BOTTOM_OFFSET, + all_elements: bool = True, + element_sets: Sequence[ElementSet | OrientedSelectionSet] = (), + all_plies: bool = True, + plies: Sequence[ModelingGroup | ModelingPly] = (), + ascii_encoding: bool = False, + ) -> None: + """ + Export the lay-up to the HDF5 Composite CAE format. + + Parameters + ---------- + path : + File path. + remove_midside_nodes : + If True, remove mid-side nodes from the exported mesh. This increases the + overall performance. + layup_representation_3d : + If True, the 3D representation of the lay-up is computed, and the offset ply + surfaces are exported. + offset_type : + Defines if the bottom, mid, or top surface of the plies is exported. + Only used if ``layup_representation_3d`` is True. + all_elements : + Whether to limit the export to some user-defined element sets or not. + element_sets : + Only plies defined on the selected element sets or oriented selection + sets will be exported. + Used only if ``all_elements`` is False. + all_plies : + Whether to export all plies or a user-defined set. + plies : + User-defined set of Modeling Plies and/or Modeling Groups. + Used only if ``all_plies`` is False. + ascii_encoding : + If True, use ASCII encoding when writing text attributes to the HDF5 CAE + file. This may be needed for compatibility with programs that don't fully + support unicode when reading the file. + """ + with wrap_grpc_errors(): + self._get_stub().ExportHDF5CompositeCAE( + model_pb2.ExportHDF5CompositeCAERequest( + resource_path=self._resource_path, + path=path_to_str_checked(path), + remove_midside_nodes=remove_midside_nodes, + layup_representation_3d=layup_representation_3d, + offset_type=offset_type_to_pb(offset_type), # type: ignore + ascii_encoding=ascii_encoding, + all_elements=all_elements, + element_sets=[element_set._resource_path for element_set in element_sets], + all_plies=all_plies, + plies=[ply._resource_path for ply in plies], + ) + ) + + @supported_since("25.1") + def import_hdf5_composite_cae( + self, + path: _PATH, + import_mode: HDF5CompositeCAEImportMode = HDF5CompositeCAEImportMode.APPEND, # type: ignore + projection_mode: HDF5CompositeCAEProjectionMode = HDF5CompositeCAEProjectionMode.SHELL, # type: ignore + minimum_angle_tolerance: float = 0.001, + recompute_reference_directions: bool = False, + shell_mapping_properties: ShellMappingProperties = ShellMappingProperties(), + solid_mapping_properties: SolidMappingProperties = SolidMappingProperties(), + coordinate_transformation: CoordinateTransformation = CoordinateTransformation(), + ) -> None: + """Import the lay-up from an HDF5 Composite CAE file. + + Parameters + ---------- + path : + File path. + import_mode : + In :py:attr:`.HDF5CompositeCAEImportMode.APPEND` mode, the imported objects are + appended to existing layup. + In :py:attr:`.HDF5CompositeCAEImportMode.OVERWRITE` mode, existing objects in the model + with the same name are replaced if possible (not locked). + projection_mode : + Determines whether loaded plies are mapped onto the reference surface + (:py:attr:`.HDF5CompositeCAEProjectionMode.SHELL` mode) or exposed as + 3D plies (:py:attr:`.HDF5CompositeCAEProjectionMode.SOLID` mode). + minimum_angle_tolerance : + Minimum angle tolerance for which tabular correction angles for plies are computed. + recompute_reference_directions : + Whether reference directions should be recomputed from tabular angle data or not. + shell_mapping_properties : + Properties for mapping to the shell on importing HDF5 Composite CAE files. + Used only if ``projection_mode`` is set to ``"shell"``. + solid_mapping_properties : + Properties for importing HDF5 Composite CAE files as imported plies. + Used only if ``projection_mode`` is set to ``"solid"``. + coordinate_transformation : + Coordinate transformation applied to the imported lay-up. + """ + if projection_mode == HDF5CompositeCAEProjectionMode.SHELL: + mapping_properties_kwargs: ( + dict[str, model_pb2.ShellMappingProperties] + | dict[str, model_pb2.SolidMappingProperties] + ) = { + "shell_mapping_properties": model_pb2.ShellMappingProperties( + all_elements=shell_mapping_properties.all_elements, + element_sets=[ + element_set._resource_path + for element_set in shell_mapping_properties.element_sets + ], + relative_thickness_tolerance=shell_mapping_properties.relative_thickness_tolerance, + relative_in_plane_tolerance=shell_mapping_properties.relative_in_plane_tolerance, + angle_tolerance=shell_mapping_properties.angle_tolerance, + small_hole_threshold=shell_mapping_properties.small_hole_threshold, + ), + } + else: + assert projection_mode == HDF5CompositeCAEProjectionMode.SOLID + mapping_properties_kwargs = { + "solid_mapping_properties": model_pb2.SolidMappingProperties( + offset_type=offset_type_to_pb(solid_mapping_properties.offset_type) # type: ignore + ), + } + + with wrap_grpc_errors(): + self._get_stub().ImportHDF5CompositeCAE( + model_pb2.ImportHDF5CompositeCAERequest( + resource_path=self._resource_path, + path=path_to_str_checked(path), + import_mode=hdf5_composite_cae_import_mode_to_pb(import_mode), # type: ignore + projection_mode=hdf5_composite_cae_projection_mode_to_pb(projection_mode), # type: ignore + minimum_angle_tolerance=minimum_angle_tolerance, + recompute_reference_directions=recompute_reference_directions, + coordinate_transformation=model_pb2.CoordinateTransformation( + **dataclasses.asdict(coordinate_transformation) + ), + **mapping_properties_kwargs, + ) + ) + def export_shell_composite_definitions(self, path: _PATH) -> None: """ Export the lay-up of the shell as HDF5 used by DPF Composites or Mechanical. diff --git a/src/ansys/acp/core/_tree_objects/utils.py b/src/ansys/acp/core/_tree_objects/utils.py new file mode 100644 index 0000000000..48dbb54ffb --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/utils.py @@ -0,0 +1,46 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import dataclasses + +__all__ = ["CoordinateTransformation"] + + +@dataclasses.dataclass +class CoordinateTransformation: + """Defines a coordinate transformation via rotation angles and translations. + + All angles are in degrees. The operations are performed in the following order: + + 1. Rotation about the x-axis + 2. Rotation about the y-axis + 3. Rotation about the z-axis + 4. Translation + + """ + + rotation_angle_x: float = 0.0 + rotation_angle_y: float = 0.0 + rotation_angle_z: float = 0.0 + translation_x: float = 0.0 + translation_y: float = 0.0 + translation_z: float = 0.0 diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 29e2ca27bc..97dd94750b 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -350,3 +350,84 @@ def test_material_import(minimal_complete_model, tempdir_if_local_acp, raises_be # THEN: the new material should be present in the model assert new_mat_id in minimal_complete_model.materials + + +@pytest.mark.parametrize( + "layup_representation_3d", + [True, False], +) +@pytest.mark.parametrize( + "offset_type", + ["bottom_offset", "middle_offset", "top_offset"], +) +def test_hdf5_composite_cae_export( + acp_instance, + minimal_complete_model, + raises_before_version, + tempdir_if_local_acp, + layup_representation_3d, + offset_type, +): + with raises_before_version("25.1"): + with tempdir_if_local_acp() as export_dir: + with tempfile.TemporaryDirectory() as tmp_dir: + export_file = export_dir / "model_export.h5" + local_file = pathlib.Path(tmp_dir) / "model_export.h5" + minimal_complete_model.export_hdf5_composite_cae( + export_file, + layup_representation_3d=layup_representation_3d, + offset_type=offset_type, + ) + acp_instance.download_file(export_file, local_file) + + assert local_file.exists() + assert os.stat(local_file).st_size > 0 + + +def test_hdf5_composite_cae_export_with_scope( + acp_instance, minimal_complete_model, raises_before_version, tempdir_if_local_acp +): + with raises_before_version("25.1"): + with tempdir_if_local_acp() as export_dir: + with tempfile.TemporaryDirectory() as tmp_dir: + export_file = export_dir / "model_export.h5" + local_file = pathlib.Path(tmp_dir) / "model_export.h5" + minimal_complete_model.export_hdf5_composite_cae( + export_file, + all_elements=False, + element_sets=minimal_complete_model.element_sets.values(), + all_plies=False, + plies=minimal_complete_model.modeling_groups[ + "ModelingGroup.1" + ].modeling_plies.values(), + ) + acp_instance.download_file(export_file, local_file) + + assert local_file.exists() + assert os.stat(local_file).st_size > 0 + + +@pytest.mark.parametrize( + "import_mode", + ["append", "overwrite"], +) +@pytest.mark.parametrize( + "projection_mode", + ["shell", "solid"], +) +def test_hdf5_composite_cae_export_import( + minimal_complete_model, + raises_before_version, + tempdir_if_local_acp, + import_mode, + projection_mode, +): + with raises_before_version("25.1"): + with tempdir_if_local_acp() as export_dir: + export_file = export_dir / "model_export.h5" + minimal_complete_model.export_hdf5_composite_cae(export_file) + minimal_complete_model.import_hdf5_composite_cae( + export_file, + import_mode=import_mode, + projection_mode=projection_mode, + ) From 2359938f7a00a0e0ed825c36957be6e8393ffe37 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 7 Nov 2024 11:55:28 +0100 Subject: [PATCH 55/96] Add 2025R2 to the list of tested server versions (#661) Add an explicit task for running test with the 2025R2 (current dev) server version. --- .github/workflows/ci_cd.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index cd07504d2d..c49b035b4c 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -130,6 +130,8 @@ jobs: server-version: "2024R2" - python-version: "3.12" server-version: "2025R1" + - python-version: "3.12" + server-version: "2025R2" steps: - uses: actions/checkout@v4 From 7a40fe31d7e0603d003a339b8e316688d718b1d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 07:12:28 +0100 Subject: [PATCH 56/96] Bump the dependencies group with 3 updates (#667) Bumps the dependencies group with 3 updates: [packaging](https://github.com/pypa/packaging), [ansys-dpf-core](https://github.com/ansys/pydpf-core) and [hypothesis](https://github.com/HypothesisWorks/hypothesis). Updates `packaging` from 24.1 to 24.2 - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/24.1...24.2) Updates `ansys-dpf-core` from 0.13.0 to 0.13.2 - [Release notes](https://github.com/ansys/pydpf-core/releases) - [Commits](https://github.com/ansys/pydpf-core/compare/v0.13.0...v0.13.2) Updates `hypothesis` from 6.116.0 to 6.118.7 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.116.0...hypothesis-python-6.118.7) --- updated-dependencies: - dependency-name: packaging dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: ansys-dpf-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 75cde9c92e..6742417968 100644 --- a/poetry.lock +++ b/poetry.lock @@ -267,15 +267,15 @@ test = ["pytest (>=7.1.2)", "pytest-cov (>=3.0.0)", "pytest-rerunfailures (>=11. [[package]] name = "ansys-dpf-core" -version = "0.13.0" +version = "0.13.2" description = "Data Processing Framework - Python Core" optional = false python-versions = "<4,>=3.9" files = [ - {file = "ansys_dpf_core-0.13.0-py3-none-any.whl", hash = "sha256:765195ee06db16a124568384caafb334a5d5b777639fd99564abc3f0c7872ea5"}, - {file = "ansys_dpf_core-0.13.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:1cf4838a6d0c0efd8cd649c311dd8fc80b404aeaacd0d20fc60041ee27904f27"}, - {file = "ansys_dpf_core-0.13.0-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:31c5d45800176db40142f9e9f0f2b797b5a02aba3323951194349b9e792a8472"}, - {file = "ansys_dpf_core-0.13.0-py3-none-win_amd64.whl", hash = "sha256:70ccecd50baf757f1abb88562298df2b66c78a08e0d4124d29bbf95af786372e"}, + {file = "ansys_dpf_core-0.13.2-py3-none-any.whl", hash = "sha256:91f6d23d14dbc3485f6141747ba2e3d98212a193b4f681fc587f46497162bb4e"}, + {file = "ansys_dpf_core-0.13.2-py3-none-manylinux1_x86_64.whl", hash = "sha256:654493f4c1b749f85b306bc62492dbf10beaaecc1a6fd3edb8b9168111768422"}, + {file = "ansys_dpf_core-0.13.2-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:38ce4df8a62a811b7d1eac5f8351a6f3df8b6655a4b639d160cb42f427099c95"}, + {file = "ansys_dpf_core-0.13.2-py3-none-win_amd64.whl", hash = "sha256:390c1a0bbad727e29e93f87549105480cdf91c05ed1e025b17709f315c607f87"}, ] [package.dependencies] @@ -1904,13 +1904,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.116.0" +version = "6.118.7" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.116.0-py3-none-any.whl", hash = "sha256:d30271214eae0d4758b72b408e9777405c7c7f687e14e8a42853adea887b2891"}, - {file = "hypothesis-6.116.0.tar.gz", hash = "sha256:9c1ac9a2edb77aacae1950d8ded6b3f40dbf8483097c88336265c348d2132c71"}, + {file = "hypothesis-6.118.7-py3-none-any.whl", hash = "sha256:5fe1d80f46d81c6160ef762e4e11a61bb4eb6838a8fb7bd3c5a2542fb107bc38"}, + {file = "hypothesis-6.118.7.tar.gz", hash = "sha256:604328f5d766a056182f54b4826f9b2d5f664f42bff68fd81b4d9d6c44b2398b"}, ] [package.dependencies] @@ -1919,10 +1919,10 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.74)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.16)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.19.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] +all = ["black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.77)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.18)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.19.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] -crosshair = ["crosshair-tool (>=0.0.74)", "hypothesis-crosshair (>=0.0.16)"] +crosshair = ["crosshair-tool (>=0.0.77)", "hypothesis-crosshair (>=0.0.18)"] dateutil = ["python-dateutil (>=1.4)"] django = ["django (>=4.2)"] dpcontracts = ["dpcontracts (>=0.4)"] @@ -3116,13 +3116,13 @@ files = [ [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] From 276bb0630cb5d6335464c725539a515241b1d98f Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 11 Nov 2024 17:40:35 +0100 Subject: [PATCH 57/96] Add scoped mesh properties (#642) Adds `shell_mesh` and `solid_mesh` properties to the `Model`, which are scoped to return only either shell or solid elements, respectively. Adds the same properties `mesh`, `shell_mesh`, and `solid_mesh` to the other objects, where applicable. The property implementation is moved from `model.py` to a new file `_mesh_data.py`. The previous `_mesh_data.py` file is renamed to `_elemental_or_nodal_data.py`. Closes #593, closes #254 --- doc/source/api/internal.rst | 4 +- src/ansys/acp/core/_tree_objects/__init__.py | 4 +- .../_tree_objects/_elemental_or_nodal_data.py | 466 +++++++++++++++++ .../acp/core/_tree_objects/_mesh_data.py | 484 +++--------------- .../acp/core/_tree_objects/analysis_ply.py | 17 +- .../_tree_objects/boolean_selection_rule.py | 21 +- .../_tree_objects/cutoff_selection_rule.py | 20 +- .../cylindrical_selection_rule.py | 20 +- .../acp/core/_tree_objects/element_set.py | 17 +- .../geometrical_selection_rule.py | 20 +- .../_tree_objects/imported_solid_model.py | 7 +- .../acp/core/_tree_objects/interface_layer.py | 18 +- src/ansys/acp/core/_tree_objects/model.py | 64 +-- .../acp/core/_tree_objects/modeling_group.py | 10 +- .../acp/core/_tree_objects/modeling_ply.py | 20 +- .../_tree_objects/oriented_selection_set.py | 23 +- .../_tree_objects/parallel_selection_rule.py | 21 +- .../acp/core/_tree_objects/production_ply.py | 19 +- .../core/_tree_objects/solid_element_set.py | 2 +- .../acp/core/_tree_objects/solid_model.py | 14 +- .../_tree_objects/spherical_selection_rule.py | 20 +- .../core/_tree_objects/tube_selection_rule.py | 20 +- .../variable_offset_selection_rule.py | 20 +- tests/unittests/test_analysis_ply.py | 25 + 24 files changed, 761 insertions(+), 595 deletions(-) create mode 100644 src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py diff --git a/doc/source/api/internal.rst b/doc/source/api/internal.rst index 81ad1cc1b5..a11657001b 100644 --- a/doc/source/api/internal.rst +++ b/doc/source/api/internal.rst @@ -15,6 +15,8 @@ Internal objects _model_printer.Node _server.common.ControllableServerProtocol _server.common.ServerProtocol + _tree_objects._elemental_or_nodal_data.MeshDataT + _tree_objects._elemental_or_nodal_data.ScalarDataT _tree_objects._grpc_helpers.edge_property_list.EdgePropertyList _tree_objects._grpc_helpers.edge_property_list.GenericEdgePropertyType _tree_objects._grpc_helpers.linked_object_list.ChildT @@ -26,8 +28,6 @@ Internal objects _tree_objects._grpc_helpers.polymorphic_from_pb.CreatableFromResourcePath _tree_objects._grpc_helpers.protocols.CreateRequest _tree_objects._grpc_helpers.protocols.ObjectInfo - _tree_objects._mesh_data.MeshDataT - _tree_objects._mesh_data.ScalarDataT _tree_objects._solid_model_export.SolidModelExportMixin _tree_objects.base.CreatableTreeObject _tree_objects.base.ServerWrapper diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 1cfaa187c3..3968b7e434 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from ._mesh_data import ScalarData, VectorData +from ._elemental_or_nodal_data import ScalarData, VectorData +from ._mesh_data import MeshData from .analysis_ply import AnalysisPly, AnalysisPlyElementalData, AnalysisPlyNodalData from .boolean_selection_rule import ( BooleanSelectionRule, @@ -123,7 +124,6 @@ HDF5CompositeCAEImportMode, HDF5CompositeCAEProjectionMode, IgnorableEntity, - MeshData, Model, ModelElementalData, ModelNodalData, diff --git a/src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py b/src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py new file mode 100644 index 0000000000..d921e9cad9 --- /dev/null +++ b/src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py @@ -0,0 +1,466 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +import dataclasses +import typing +from typing import Any, ClassVar, Literal, cast + +import numpy as np +import numpy.typing as npt +from pyvista.core.pointset import PolyData, UnstructuredGrid +from typing_extensions import Self + +from ansys.acp.core._utils.array_conversions import dataarray_to_numpy, to_numpy +from ansys.api.acp.v0 import mesh_query_pb2, mesh_query_pb2_grpc + +from .._typing_helper import StrEnum +from .._utils.property_protocols import ReadOnlyProperty +from ._mesh_data import MeshData +from .base import TreeObject +from .enums import ( + elemental_data_type_from_pb, + elemental_data_type_to_pb, + nodal_data_type_from_pb, + nodal_data_type_to_pb, +) + +__all__ = [ + "ElementalData", + "NodalData", + "elemental_data_property", + "nodal_data_property", + "ScalarData", + "VectorData", +] + + +@dataclasses.dataclass +class _LabelInfo: + mesh_labels: npt.NDArray[np.int32] + data_labels: npt.NDArray[np.int32] + mesh_label_to_index_map: dict[int, int] + + +def _get_labels( + *, + field_names: _LabelAndPyvistaFieldNames, + labels: npt.NDArray[np.int32], + mesh: MeshData, +) -> _LabelInfo: + mesh_labels = getattr(mesh, field_names.LABEL_FIELD_NAME) + mesh_label_to_index_map = {label: idx for idx, label in enumerate(mesh_labels)} + return _LabelInfo( + mesh_labels=mesh_labels, data_labels=labels, mesh_label_to_index_map=mesh_label_to_index_map + ) + + +def _expand_array( + *, + array: npt.NDArray[ScalarDataT], + labels: _LabelInfo, + culling_factor: int = 1, +) -> npt.NDArray[np.float64]: + """Expand the array to the size of the mesh.""" + target_shape = tuple([labels.mesh_labels.size] + list(array.shape[1:])) + target_array = np.ones(target_shape, dtype=np.float64) * np.nan + for idx, (label, value) in enumerate(zip(labels.data_labels, array)): + if idx % culling_factor == 0: + try: + target_array[labels.mesh_label_to_index_map[label]] = value + except KeyError: + pass + return target_array + + +def _get_pyvista_mesh_with_all_data( + *, + mesh_data_base: ElementalOrNodalDataBase, + mesh: MeshData, +) -> UnstructuredGrid: + pv_mesh = mesh.to_pyvista() + + mesh_data_field = getattr( + pv_mesh, mesh_data_base._LABEL_AND_PYVISTA_FIELD_NAMES.PYVISTA_FIELD_NAME + ) + field_labels = getattr( + mesh_data_base, mesh_data_base._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME + ).values + labels = _get_labels( + field_names=mesh_data_base._LABEL_AND_PYVISTA_FIELD_NAMES, mesh=mesh, labels=field_labels + ) + + for name in mesh_data_base._field_names(): + values = getattr(mesh_data_base, name).values + target_array = _expand_array(array=values, labels=labels) + mesh_data_field[name] = target_array + return pv_mesh + + +def _get_mesh_with_scalar_pyvista_data( + *, + labels: npt.NDArray[np.int32], + field_names: _LabelAndPyvistaFieldNames, + mesh: MeshData, + values: npt.NDArray[ScalarDataT], + component_name: str, +) -> UnstructuredGrid: + all_labels = _get_labels(field_names=field_names, labels=labels, mesh=mesh) + + pv_mesh = mesh.to_pyvista() + mesh_data_field = getattr(pv_mesh, field_names.PYVISTA_FIELD_NAME) + + target_array = _expand_array(array=values, labels=all_labels) + component_label = component_name + mesh_data_field[component_label] = target_array + return pv_mesh + + +def _get_pyvista_glyphs( + *, + labels: npt.NDArray[np.int32], + field_names: _LabelAndPyvistaFieldNames, + mesh: MeshData, + values: npt.NDArray[np.float64], + component_name: str, + culling_factor: int = 1, + scaling_factor: float = 1.0, + **kwargs: Any, +) -> PolyData: + all_labels = _get_labels(field_names=field_names, labels=labels, mesh=mesh) + + pv_mesh = mesh.to_pyvista() + mesh_data_field = getattr(pv_mesh, field_names.PYVISTA_FIELD_NAME) + + target_array = _expand_array(array=values, labels=all_labels, culling_factor=culling_factor) + component_label = component_name + mesh_data_field[component_label] = target_array + + magnitude_name = f"{component_label}_magnitude" + mesh_data_field[magnitude_name] = np.linalg.norm(target_array, axis=-1) * scaling_factor + return pv_mesh.glyph(orient=component_label, scale=magnitude_name, **kwargs) # type: ignore + + +ScalarDataT = typing.TypeVar("ScalarDataT", np.float64, np.int32) + + +class ScalarData(typing.Generic[ScalarDataT]): + """Class that encapsulates scalar data.""" + + def __init__( + self, + field_names: _LabelAndPyvistaFieldNames, + labels: npt.NDArray[np.int32], + values: npt.NDArray[ScalarDataT], + component_name: str, + ): + self._field_names = field_names + self._labels = labels + self._values: npt.NDArray[ScalarDataT] = values + self._component_name = component_name + + @property + def values(self) -> npt.NDArray[ScalarDataT]: + """Scalar data values as a numpy array.""" + return self._values + + @property + def component_name(self) -> str: + """Name of the component.""" + return self._component_name + + def get_pyvista_mesh( + self, + mesh: MeshData, + ) -> UnstructuredGrid: + """Convert the mesh data to a PyVista object. + + Parameters + ---------- + mesh : + The mesh to which the data is associated. + """ + return _get_mesh_with_scalar_pyvista_data( + labels=self._labels, + field_names=self._field_names, + mesh=mesh, + values=self._values, + component_name=self._component_name, + ) + + +class VectorData: + """Class that encapsulates vector data.""" + + def __init__( + self, + field_names: _LabelAndPyvistaFieldNames, + labels: npt.NDArray[np.int32], + values: npt.NDArray[np.float64], + component_name: str, + ): + self._field_names = field_names + self._labels = labels + self._values = values + self._component_name = component_name + + @property + def values(self) -> npt.NDArray[np.float64]: + """Vector data values as a numpy array.""" + return self._values + + @property + def component_name(self) -> str: + """Name of the component.""" + return self._component_name + + def get_pyvista_glyphs( + self, + *, + mesh: MeshData, + culling_factor: int = 1, + scaling_factor: float = 1.0, + **kwargs: Any, + ) -> PolyData: + """Get a pyvista glyph object from the vector data. + + Parameters + ---------- + mesh : + The mesh to which the data is associated. + culling_factor : + If set to a value other than ``1``, add only every n-th data + point to the PyVista object. This is useful especially for + vector data, where the arrows can be too dense. + scaling_factor : + Factor to scale the length of the arrows. + kwargs : + Keyword arguments passed to the PyVista object constructor. + """ + return _get_pyvista_glyphs( + labels=self._labels, + field_names=self._field_names, + mesh=mesh, + values=self._values, + component_name=self._component_name, + culling_factor=culling_factor, + scaling_factor=scaling_factor, + **kwargs, + ) + + +def _check_field_type(klass: Any, field_name: str, actual_field_type: str) -> None: + """Check that the type declared in the dataclass (klass) matches the actual type.""" + declared_field_types: typing.Sequence[str] = cast( + typing.Sequence[str], + [field.type for field in dataclasses.fields(klass) if field.name == field_name], + ) + if len(declared_field_types) != 1: + raise RuntimeError("Failed to find field in dataclass.") + declared_field_type = declared_field_types[0].removesuffix(" | None") + if declared_field_type != actual_field_type: + raise RuntimeError( + f"Declared type does not match actual data type. " + f"Declared type: {declared_field_type}, actual type: {actual_field_type}. " + f"Field name: {field_name}" + ) + + +@dataclasses.dataclass +class _LabelAndPyvistaFieldNames: + LABEL_FIELD_NAME: str + PYVISTA_FIELD_NAME: str + + +@dataclasses.dataclass +class ElementalOrNodalDataBase: + """Base class for nodal or elemental mesh data. + + Implements the construction from a protobuf response and the conversion + to a PyVista object. + """ + + _LABEL_AND_PYVISTA_FIELD_NAMES: ClassVar[_LabelAndPyvistaFieldNames] + _FIELD_NAME_FROM_PB_VALUE: ClassVar[typing.Callable[[int], StrEnum]] + _PB_VALUE_FROM_FIELD_NAME: ClassVar[typing.Callable[[StrEnum], int]] + + @classmethod + def _field_names(cls) -> list[str]: + return [ + field.name + for field in dataclasses.fields(cls) + if field.name != cls._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME + ] + + @classmethod + def _from_pb(cls, response: mesh_query_pb2.ElementalData | mesh_query_pb2.NodalData) -> Self: + """Construct a mesh data object from a protobuf response.""" + labels = to_numpy(response.labels) + kwargs: dict[str, Any] = { + cls._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME: ScalarData( + field_names=cls._LABEL_AND_PYVISTA_FIELD_NAMES, + labels=labels, + values=labels, + component_name=cls._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME, + ) + } + for data_type, array in zip(response.data_types, response.data_arrays): + field_name = cls._FIELD_NAME_FROM_PB_VALUE(data_type).value + values = cast( + npt.NDArray[np.float64], dataarray_to_numpy(array, dtype=np.float64) + ) # todo: handle other dtypes + kwargs[field_name] = values + data_wrapper: VectorData | ScalarData[np.float64] + if len(values.shape) == 2 and values.shape[1] == 3: + data_wrapper = VectorData( + field_names=cls._LABEL_AND_PYVISTA_FIELD_NAMES, + labels=labels, + values=values, + component_name=field_name, + ) + _check_field_type(klass=cls, field_name=field_name, actual_field_type="VectorData") + + else: + data_wrapper = ScalarData( + field_names=cls._LABEL_AND_PYVISTA_FIELD_NAMES, + labels=labels, + values=values, + component_name=field_name, + ) + _check_field_type( + klass=cls, field_name=field_name, actual_field_type="ScalarData[np.float64]" + ) + kwargs[field_name] = data_wrapper + + instance = cls(**kwargs) + return instance + + def get_pyvista_mesh( + self, + mesh: MeshData, + ) -> UnstructuredGrid: + """Get a pyvista mesh with all data. + + Parameters + ---------- + mesh : + The mesh to which the data is associated. + """ + return _get_pyvista_mesh_with_all_data(mesh_data_base=self, mesh=mesh) + + +_NODE_FIELD_NAMES = _LabelAndPyvistaFieldNames( + LABEL_FIELD_NAME="node_labels", + PYVISTA_FIELD_NAME="point_data", +) + +_ELEMENT_FIELD_NAMES = _LabelAndPyvistaFieldNames( + LABEL_FIELD_NAME="element_labels", + PYVISTA_FIELD_NAME="cell_data", +) + + +@dataclasses.dataclass +class NodalData(ElementalOrNodalDataBase): + """Base class for nodal data.""" + + node_labels: ScalarData[np.int32] + _LABEL_AND_PYVISTA_FIELD_NAMES = _NODE_FIELD_NAMES + _PB_VALUE_FROM_FIELD_NAME = nodal_data_type_to_pb + _FIELD_NAME_FROM_PB_VALUE = nodal_data_type_from_pb + + +@dataclasses.dataclass +class ElementalData(ElementalOrNodalDataBase): + """Base class for elemental data.""" + + element_labels: ScalarData[np.int32] + _LABEL_AND_PYVISTA_FIELD_NAMES = _ELEMENT_FIELD_NAMES + _PB_VALUE_FROM_FIELD_NAME = elemental_data_type_to_pb + _FIELD_NAME_FROM_PB_VALUE = elemental_data_type_from_pb + + +T = typing.TypeVar("T", bound=ElementalOrNodalDataBase) + + +ElementalDataT = typing.TypeVar("ElementalDataT", bound=ElementalData) + + +def elemental_data_property( + wrapped_cls: type[ElementalDataT], +) -> ReadOnlyProperty[ElementalDataT]: + """Create a property to get elemental data from a tree object.""" + return _mesh_data_property_impl( + wrapped_cls=wrapped_cls, + request_name="GetElementalData", + request_type=mesh_query_pb2.GetElementalDataRequest, + ) + + +NodalDataT = typing.TypeVar("NodalDataT", bound=NodalData) + + +def nodal_data_property( + wrapped_cls: type[NodalDataT], +) -> ReadOnlyProperty[NodalDataT]: + """Create a property to get nodal data from a tree object.""" + return _mesh_data_property_impl( + wrapped_cls=wrapped_cls, + request_name="GetNodalData", + request_type=mesh_query_pb2.GetNodalDataRequest, + ) + + +MeshDataT = typing.TypeVar("MeshDataT", bound=ElementalOrNodalDataBase) + + +def _mesh_data_property_impl( + wrapped_cls: type[MeshDataT], + request_name: Literal["GetNodalData", "GetElementalData"], + request_type: ( + type[mesh_query_pb2.GetNodalDataRequest] | type[mesh_query_pb2.GetElementalDataRequest] + ), +) -> ReadOnlyProperty[MeshDataT]: + """Create a mesh data property. + + Implementation of the mesh data property helpers ``nodal_data_property`` + and ``elemental_data_property``. + """ + + def getter(self: TreeObject) -> MeshDataT: + if not self._is_stored: + raise RuntimeError("Cannot get mesh data from an unstored object") + stub = mesh_query_pb2_grpc.MeshQueryServiceStub(self._channel) + request_func = getattr(stub, request_name) + response = request_func( + request=request_type( + resource_path=self._resource_path, + data_types=[ + wrapped_cls._PB_VALUE_FROM_FIELD_NAME(name) # type: ignore + for name in wrapped_cls._field_names() + ], + ), + ) + return wrapped_cls._from_pb(response) + + return property(getter) diff --git a/src/ansys/acp/core/_tree_objects/_mesh_data.py b/src/ansys/acp/core/_tree_objects/_mesh_data.py index 415382bf13..0eefcb66ab 100644 --- a/src/ansys/acp/core/_tree_objects/_mesh_data.py +++ b/src/ansys/acp/core/_tree_objects/_mesh_data.py @@ -23,443 +23,95 @@ from __future__ import annotations import dataclasses -import typing -from typing import Any, ClassVar, Literal, cast import numpy as np import numpy.typing as npt -from pyvista.core.pointset import PolyData, UnstructuredGrid -from typing_extensions import Self +from packaging.version import parse as parse_version +from pyvista.core.pointset import UnstructuredGrid -from ansys.acp.core._utils.array_conversions import dataarray_to_numpy, to_numpy -from ansys.api.acp.v0 import mesh_query_pb2, mesh_query_pb2_grpc +from ansys.api.acp.v0 import base_pb2, mesh_query_pb2, mesh_query_pb2_grpc -from .._typing_helper import StrEnum +from .._utils.array_conversions import to_numpy from .._utils.property_protocols import ReadOnlyProperty +from .._utils.visualization import to_pyvista_faces, to_pyvista_types from .base import TreeObject -from .enums import ( - elemental_data_type_from_pb, - elemental_data_type_to_pb, - nodal_data_type_from_pb, - nodal_data_type_to_pb, -) - -if typing.TYPE_CHECKING: # pragma: no cover - from .model import MeshData # avoid circular import __all__ = [ - "ElementalData", - "NodalData", - "elemental_data_property", - "nodal_data_property", - "ScalarData", - "VectorData", + "MeshData", + "full_mesh_property", + "shell_mesh_property", + "solid_mesh_property", ] @dataclasses.dataclass -class _Labels: - mesh_labels: npt.NDArray[np.int32] - data_labels: npt.NDArray[np.int32] - mesh_label_to_index_map: dict[int, int] - - -def _get_labels( - *, - field_names: _LabelAndPyvistaFieldNames, - labels: npt.NDArray[np.int32], - mesh: MeshData, -) -> _Labels: - mesh_labels = getattr(mesh, field_names.LABEL_FIELD_NAME) - mesh_label_to_index_map = {label: idx for idx, label in enumerate(mesh_labels)} - return _Labels( - mesh_labels=mesh_labels, data_labels=labels, mesh_label_to_index_map=mesh_label_to_index_map - ) - - -def _expand_array( - *, - array: npt.NDArray[ScalarDataT], - labels: _Labels, - culling_factor: int = 1, -) -> npt.NDArray[np.float64]: - """Expand the array to the size of the mesh.""" - target_shape = tuple([labels.mesh_labels.size] + list(array.shape[1:])) - target_array = np.ones(target_shape, dtype=np.float64) * np.nan - for idx, (label, value) in enumerate(zip(labels.data_labels, array)): - if idx % culling_factor == 0: - target_array[labels.mesh_label_to_index_map[label]] = value - return target_array - - -def _get_pyvista_mesh_with_all_data( - *, - mesh_data_base: MeshDataBase, - mesh: MeshData, -) -> UnstructuredGrid: - pv_mesh = mesh.to_pyvista() - - mesh_data_field = getattr( - pv_mesh, mesh_data_base._LABEL_AND_PYVISTA_FIELD_NAMES.PYVISTA_FIELD_NAME - ) - field_labels = getattr( - mesh_data_base, mesh_data_base._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME - ).values - labels = _get_labels( - field_names=mesh_data_base._LABEL_AND_PYVISTA_FIELD_NAMES, mesh=mesh, labels=field_labels - ) - - for name in mesh_data_base._field_names(): - values = getattr(mesh_data_base, name).values - target_array = _expand_array(array=values, labels=labels) - mesh_data_field[name] = target_array - return pv_mesh - - -def _get_mesh_with_scalar_pyvista_data( - *, - labels: npt.NDArray[np.int32], - field_names: _LabelAndPyvistaFieldNames, - mesh: MeshData, - values: npt.NDArray[ScalarDataT], - component_name: str, -) -> UnstructuredGrid: - all_labels = _get_labels(field_names=field_names, labels=labels, mesh=mesh) - - pv_mesh = mesh.to_pyvista() - mesh_data_field = getattr(pv_mesh, field_names.PYVISTA_FIELD_NAME) - - target_array = _expand_array(array=values, labels=all_labels) - component_label = component_name - mesh_data_field[component_label] = target_array - return pv_mesh - - -def _get_pyvista_glyphs( - *, - labels: npt.NDArray[np.int32], - field_names: _LabelAndPyvistaFieldNames, - mesh: MeshData, - values: npt.NDArray[np.float64], - component_name: str, - culling_factor: int = 1, - scaling_factor: float = 1.0, - **kwargs: Any, -) -> PolyData: - all_labels = _get_labels(field_names=field_names, labels=labels, mesh=mesh) - - pv_mesh = mesh.to_pyvista() - mesh_data_field = getattr(pv_mesh, field_names.PYVISTA_FIELD_NAME) - - target_array = _expand_array(array=values, labels=all_labels, culling_factor=culling_factor) - component_label = component_name - mesh_data_field[component_label] = target_array - - magnitude_name = f"{component_label}_magnitude" - mesh_data_field[magnitude_name] = np.linalg.norm(target_array, axis=-1) * scaling_factor - return pv_mesh.glyph(orient=component_label, scale=magnitude_name, **kwargs) # type: ignore - - -ScalarDataT = typing.TypeVar("ScalarDataT", np.float64, np.int32) - - -class ScalarData(typing.Generic[ScalarDataT]): - """Class that encapsulates scalar data.""" - - def __init__( - self, - field_names: _LabelAndPyvistaFieldNames, - labels: npt.NDArray[np.int32], - values: npt.NDArray[ScalarDataT], - component_name: str, - ): - self._field_names = field_names - self._labels = labels - self._values: npt.NDArray[ScalarDataT] = values - self._component_name = component_name - - @property - def values(self) -> npt.NDArray[ScalarDataT]: - """Scalar data values as a numpy array.""" - return self._values - - @property - def component_name(self) -> str: - """Name of the component.""" - return self._component_name - - def get_pyvista_mesh( - self, - mesh: MeshData, - ) -> UnstructuredGrid: - """Convert the mesh data to a PyVista object. - - Parameters - ---------- - mesh : - The mesh to which the data is associated. - """ - return _get_mesh_with_scalar_pyvista_data( - labels=self._labels, - field_names=self._field_names, - mesh=mesh, - values=self._values, - component_name=self._component_name, - ) - - -class VectorData: - """Class that encapsulates vector data.""" - - def __init__( - self, - field_names: _LabelAndPyvistaFieldNames, - labels: npt.NDArray[np.int32], - values: npt.NDArray[np.float64], - component_name: str, - ): - self._field_names = field_names - self._labels = labels - self._values = values - self._component_name = component_name - - @property - def values(self) -> npt.NDArray[np.float64]: - """Vector data values as a numpy array.""" - return self._values - - @property - def component_name(self) -> str: - """Name of the component.""" - return self._component_name - - def get_pyvista_glyphs( - self, - *, - mesh: MeshData, - culling_factor: int = 1, - scaling_factor: float = 1.0, - **kwargs: Any, - ) -> PolyData: - """Get a pyvista glyph object from the vector data. - - Parameters - ---------- - mesh : - The mesh to which the data is associated. - culling_factor : - If set to a value other than ``1``, add only every n-th data - point to the PyVista object. This is useful especially for - vector data, where the arrows can be too dense. - scaling_factor : - Factor to scale the length of the arrows. - kwargs : - Keyword arguments passed to the PyVista object constructor. - """ - return _get_pyvista_glyphs( - labels=self._labels, - field_names=self._field_names, - mesh=mesh, - values=self._values, - component_name=self._component_name, - culling_factor=culling_factor, - scaling_factor=scaling_factor, - **kwargs, - ) - - -def _check_field_type(klass: Any, field_name: str, actual_field_type: str) -> None: - """Check that the type declared in the dataclass (klass) matches the actual type.""" - declared_field_types: typing.Sequence[str] = cast( - typing.Sequence[str], - [field.type for field in dataclasses.fields(klass) if field.name == field_name], - ) - if len(declared_field_types) != 1: - raise RuntimeError("Failed to find field in dataclass.") - declared_field_type = declared_field_types[0].removesuffix(" | None") - if declared_field_type != actual_field_type: - raise RuntimeError( - f"Declared type does not match actual data type. " - f"Declared type: {declared_field_type}, actual type: {actual_field_type}. " - f"Field name: {field_name}" +class MeshData: + """Container for the mesh data of an ACP Model.""" + + node_labels: npt.NDArray[np.int32] + node_coordinates: npt.NDArray[np.float64] + element_labels: npt.NDArray[np.int32] + element_types: npt.NDArray[np.int32] + element_nodes: npt.NDArray[np.int32] + element_nodes_offsets: npt.NDArray[np.int32] + + def to_pyvista(self) -> UnstructuredGrid: + """Convert the mesh data to a PyVista mesh.""" + return UnstructuredGrid( + to_pyvista_faces( + element_types=self.element_types, + element_nodes=self.element_nodes, + element_nodes_offsets=self.element_nodes_offsets, + ), + to_pyvista_types(self.element_types), + self.node_coordinates, ) -@dataclasses.dataclass -class _LabelAndPyvistaFieldNames: - LABEL_FIELD_NAME: str - PYVISTA_FIELD_NAME: str - - -@dataclasses.dataclass -class MeshDataBase: - """Base class for nodal or elemental mesh data. - - Implements the construction from a protobuf response and the conversion - to a PyVista object. - """ - - _LABEL_AND_PYVISTA_FIELD_NAMES: ClassVar[_LabelAndPyvistaFieldNames] - _FIELD_NAME_FROM_PB_VALUE: ClassVar[typing.Callable[[int], StrEnum]] - _PB_VALUE_FROM_FIELD_NAME: ClassVar[typing.Callable[[StrEnum], int]] - - @classmethod - def _field_names(cls) -> list[str]: - return [ - field.name - for field in dataclasses.fields(cls) - if field.name != cls._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME - ] +def _mesh_property_impl( + element_scoping: mesh_query_pb2.ElementScopingType.ValueType, doc: str +) -> ReadOnlyProperty[MeshData]: + def getter(self: TreeObject) -> MeshData: + mesh_query_stub = mesh_query_pb2_grpc.MeshQueryServiceStub(self._channel) + assert self._server_version is not None + if self._server_version < parse_version("25.1"): + from .model import Model - @classmethod - def _from_pb(cls, response: mesh_query_pb2.ElementalData | mesh_query_pb2.NodalData) -> Self: - """Construct a mesh data object from a protobuf response.""" - labels = to_numpy(response.labels) - kwargs: dict[str, Any] = { - cls._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME: ScalarData( - field_names=cls._LABEL_AND_PYVISTA_FIELD_NAMES, - labels=labels, - values=labels, - component_name=cls._LABEL_AND_PYVISTA_FIELD_NAMES.LABEL_FIELD_NAME, - ) - } - for data_type, array in zip(response.data_types, response.data_arrays): - field_name = cls._FIELD_NAME_FROM_PB_VALUE(data_type).value - values = cast( - npt.NDArray[np.float64], dataarray_to_numpy(array, dtype=np.float64) - ) # todo: handle other dtypes - kwargs[field_name] = values - data_wrapper: VectorData | ScalarData[np.float64] - if len(values.shape) == 2 and values.shape[1] == 3: - data_wrapper = VectorData( - field_names=cls._LABEL_AND_PYVISTA_FIELD_NAMES, - labels=labels, - values=values, - component_name=field_name, + if not isinstance(self, Model): + raise RuntimeError( + "Mesh attributes for object types other than 'Model' are only supported " + "for server versions 25.1 and later." ) - _check_field_type(klass=cls, field_name=field_name, actual_field_type="VectorData") - - else: - data_wrapper = ScalarData( - field_names=cls._LABEL_AND_PYVISTA_FIELD_NAMES, - labels=labels, - values=values, - component_name=field_name, - ) - _check_field_type( - klass=cls, field_name=field_name, actual_field_type="ScalarData[np.float64]" + if element_scoping != mesh_query_pb2.ElementScopingType.ALL: + raise RuntimeError( + "Element scoping is only supported for server versions 25.1 and later." ) - kwargs[field_name] = data_wrapper - - instance = cls(**kwargs) - return instance - - def get_pyvista_mesh( - self, - mesh: MeshData, - ) -> UnstructuredGrid: - """Get a pyvista mesh with all data. + request: base_pb2.GetRequest | mesh_query_pb2.GetMeshDataRequest = base_pb2.GetRequest( + resource_path=self._resource_path + ) + else: + request = mesh_query_pb2.GetMeshDataRequest( + resource_path=self._resource_path, element_scoping=element_scoping + ) + reply = mesh_query_stub.GetMeshData(request) + return MeshData( + node_labels=to_numpy(reply.node_labels), + node_coordinates=to_numpy(reply.node_coordinates), + element_labels=to_numpy(reply.element_labels), + element_types=to_numpy(reply.element_types), + element_nodes=to_numpy(reply.element_nodes), + element_nodes_offsets=to_numpy(reply.element_nodes_offsets), + ) - Parameters - ---------- - mesh : - The mesh to which the data is associated. - """ - return _get_pyvista_mesh_with_all_data(mesh_data_base=self, mesh=mesh) + return property(getter, doc=doc) -_NODE_FIELD_NAMES = _LabelAndPyvistaFieldNames( - LABEL_FIELD_NAME="node_labels", - PYVISTA_FIELD_NAME="point_data", +full_mesh_property = _mesh_property_impl( + mesh_query_pb2.ElementScopingType.ALL, doc="Full mesh associated with the object." ) - -_ELEMENT_FIELD_NAMES = _LabelAndPyvistaFieldNames( - LABEL_FIELD_NAME="element_labels", - PYVISTA_FIELD_NAME="cell_data", +shell_mesh_property = _mesh_property_impl( + mesh_query_pb2.ElementScopingType.SHELL, doc="Shell mesh associated with the object." +) +solid_mesh_property = _mesh_property_impl( + mesh_query_pb2.ElementScopingType.SOLID, doc="Solid mesh associated with the object." ) - - -@dataclasses.dataclass -class NodalData(MeshDataBase): - """Base class for nodal data.""" - - node_labels: ScalarData[np.int32] - _LABEL_AND_PYVISTA_FIELD_NAMES = _NODE_FIELD_NAMES - _PB_VALUE_FROM_FIELD_NAME = nodal_data_type_to_pb - _FIELD_NAME_FROM_PB_VALUE = nodal_data_type_from_pb - - -@dataclasses.dataclass -class ElementalData(MeshDataBase): - """Base class for elemental data.""" - - element_labels: ScalarData[np.int32] - _LABEL_AND_PYVISTA_FIELD_NAMES = _ELEMENT_FIELD_NAMES - _PB_VALUE_FROM_FIELD_NAME = elemental_data_type_to_pb - _FIELD_NAME_FROM_PB_VALUE = elemental_data_type_from_pb - - -T = typing.TypeVar("T", bound=MeshDataBase) - - -ElementalDataT = typing.TypeVar("ElementalDataT", bound=ElementalData) - - -def elemental_data_property( - wrapped_cls: type[ElementalDataT], -) -> ReadOnlyProperty[ElementalDataT]: - """Create a property to get elemental data from a tree object.""" - return _mesh_data_property_impl( - wrapped_cls=wrapped_cls, - request_name="GetElementalData", - request_type=mesh_query_pb2.GetElementalDataRequest, - ) - - -NodalDataT = typing.TypeVar("NodalDataT", bound=NodalData) - - -def nodal_data_property( - wrapped_cls: type[NodalDataT], -) -> ReadOnlyProperty[NodalDataT]: - """Create a property to get nodal data from a tree object.""" - return _mesh_data_property_impl( - wrapped_cls=wrapped_cls, - request_name="GetNodalData", - request_type=mesh_query_pb2.GetNodalDataRequest, - ) - - -MeshDataT = typing.TypeVar("MeshDataT", bound=MeshDataBase) - - -def _mesh_data_property_impl( - wrapped_cls: type[MeshDataT], - request_name: Literal["GetNodalData", "GetElementalData"], - request_type: ( - type[mesh_query_pb2.GetNodalDataRequest] | type[mesh_query_pb2.GetElementalDataRequest] - ), -) -> ReadOnlyProperty[MeshDataT]: - """Create a mesh data property. - - Implementation of the mesh data property helpers ``nodal_data_property`` - and ``elemental_data_property``. - """ - - def getter(self: TreeObject) -> MeshDataT: - if not self._is_stored: - raise RuntimeError("Cannot get mesh data from an unstored object") - stub = mesh_query_pb2_grpc.MeshQueryServiceStub(self._channel) - request_func = getattr(stub, request_name) - response = request_func( - request=request_type( - resource_path=self._resource_path, - data_types=[ - wrapped_cls._PB_VALUE_FROM_FIELD_NAME(name) # type: ignore - for name in wrapped_cls._field_names() - ], - ), - ) - return wrapped_cls._from_pb(response) - - return property(getter) diff --git a/src/ansys/acp/core/_tree_objects/analysis_ply.py b/src/ansys/acp/core/_tree_objects/analysis_ply.py index ade31b1f09..ab56c7023e 100644 --- a/src/ansys/acp/core/_tree_objects/analysis_ply.py +++ b/src/ansys/acp/core/_tree_objects/analysis_ply.py @@ -30,12 +30,7 @@ from ansys.api.acp.v0 import analysis_ply_pb2, analysis_ply_pb2_grpc from .._utils.property_protocols import ReadOnlyProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property_read_only, - grpc_link_property_read_only, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, ScalarData, @@ -43,6 +38,12 @@ elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property_read_only, + grpc_link_property_read_only, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property, solid_mesh_property from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -119,5 +120,9 @@ def _create_stub(self) -> analysis_ply_pb2_grpc.ObjectServiceStub: active_in_post_mode: ReadOnlyProperty[bool] = grpc_data_property_read_only( "properties.active_in_post_mode" ) + + mesh = full_mesh_property + shell_mesh = shell_mesh_property + solid_mesh = solid_mesh_property elemental_data = elemental_data_property(AnalysisPlyElementalData) nodal_data = nodal_data_property(AnalysisPlyNodalData) diff --git a/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py b/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py index b1f30e01ff..a8f5615e92 100644 --- a/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py @@ -28,19 +28,20 @@ from ansys.api.acp.v0 import boolean_selection_rule_pb2, boolean_selection_rule_pb2_grpc from .._utils.property_protocols import ReadWriteProperty -from ._grpc_helpers.edge_property_list import define_add_method, define_edge_property_list -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.edge_property_list import define_add_method, define_edge_property_list +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .linked_selection_rule import LinkedSelectionRule @@ -49,7 +50,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "BooleanSelectionRule", @@ -120,5 +121,9 @@ def _create_stub(self) -> boolean_selection_rule_pb2_grpc.ObjectServiceStub: include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data + elemental_data = elemental_data_property(BooleanSelectionRuleElementalData) nodal_data = nodal_data_property(BooleanSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py b/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py index 52ea3051b1..a297f4ec00 100644 --- a/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py @@ -28,19 +28,20 @@ from ansys.api.acp.v0 import cutoff_selection_rule_pb2, cutoff_selection_rule_pb2_grpc from .._utils.property_protocols import ReadWriteProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - grpc_link_property, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .enums import ( @@ -58,7 +59,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ @@ -161,5 +162,8 @@ def _create_stub(self) -> cutoff_selection_rule_pb2_grpc.ObjectServiceStub: ) ply_tapering: ReadWriteProperty[bool, bool] = grpc_data_property("properties.ply_tapering") + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data elemental_data = elemental_data_property(CutoffSelectionRuleElementalData) nodal_data = nodal_data_property(CutoffSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py b/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py index d0d23ad900..712d6e9775 100644 --- a/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py @@ -29,19 +29,20 @@ from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadWriteProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - grpc_link_property, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -50,7 +51,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "CylindricalSelectionRule", @@ -147,5 +148,8 @@ def _create_stub(self) -> cylindrical_selection_rule_pb2_grpc.ObjectServiceStub: ) include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data elemental_data = elemental_data_property(CylindricalSelectionRuleElementalData) nodal_data = nodal_data_property(CylindricalSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/element_set.py b/src/ansys/acp/core/_tree_objects/element_set.py index 9c75f52bb6..0b9363d1cb 100644 --- a/src/ansys/acp/core/_tree_objects/element_set.py +++ b/src/ansys/acp/core/_tree_objects/element_set.py @@ -29,18 +29,19 @@ from .._utils.array_conversions import to_1D_int_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -48,7 +49,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "ElementSet", @@ -113,5 +114,7 @@ def _create_stub(self) -> element_set_pb2_grpc.ObjectServiceStub: to_protobuf=to_1D_int_array, ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property elemental_data = elemental_data_property(ElementSetElementalData) nodal_data = nodal_data_property(ElementSetNodalData) diff --git a/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py b/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py index 7710d81cae..fed15c5bdc 100644 --- a/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py @@ -28,6 +28,13 @@ from ansys.api.acp.v0 import geometrical_selection_rule_pb2, geometrical_selection_rule_pb2_grpc from .._utils.property_protocols import ReadWriteProperty +from ._elemental_or_nodal_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from ._grpc_helpers.linked_object_list import define_linked_object_list from ._grpc_helpers.property_helper import ( grpc_data_property, @@ -35,13 +42,7 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ( - ElementalData, - NodalData, - VectorData, - elemental_data_property, - nodal_data_property, -) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .element_set import ElementSet from .enums import ( @@ -56,7 +57,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "GeometricalSelectionRule", @@ -162,5 +163,8 @@ def _create_stub(self) -> geometrical_selection_rule_pb2_grpc.ObjectServiceStub: "properties.positive_capture_tolerance" ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data elemental_data = elemental_data_property(GeometricalSelectionRuleElementalData) nodal_data = nodal_data_property(GeometricalSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index dc36bd9772..22474f83a4 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -38,6 +38,12 @@ from .._typing_helper import PATH as _PATH from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from ._elemental_or_nodal_data import ( + ElementalData, + NodalData, + elemental_data_property, + nodal_data_property, +) from ._grpc_helpers.enum_wrapper import wrap_to_string_enum from ._grpc_helpers.exceptions import wrap_grpc_errors from ._grpc_helpers.mapping import ( @@ -51,7 +57,6 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property from ._solid_model_export import SolidModelExportMixin from .base import ( CreatableTreeObject, diff --git a/src/ansys/acp/core/_tree_objects/interface_layer.py b/src/ansys/acp/core/_tree_objects/interface_layer.py index 7b55da94d4..4ee64d904c 100644 --- a/src/ansys/acp/core/_tree_objects/interface_layer.py +++ b/src/ansys/acp/core/_tree_objects/interface_layer.py @@ -28,6 +28,13 @@ from ansys.api.acp.v0 import interface_layer_pb2, interface_layer_pb2_grpc from .._utils.property_protocols import ReadWriteProperty +from ._elemental_or_nodal_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from ._grpc_helpers.linked_object_list import ( define_linked_object_list, define_polymorphic_linked_object_list, @@ -37,13 +44,7 @@ grpc_data_property_read_only, mark_grpc_properties, ) -from ._mesh_data import ( - ElementalData, - NodalData, - VectorData, - elemental_data_property, - nodal_data_property, -) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .element_set import ElementSet from .enums import status_type_from_pb @@ -125,5 +126,8 @@ def _create_stub(self) -> interface_layer_pb2_grpc.ObjectServiceStub: "properties.open_area_sets", allowed_types=(ElementSet, OrientedSelectionSet) ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property + elemental_data = elemental_data_property(InterfaceLayerElementalData) nodal_data = nodal_data_property(InterfaceLayerNodalData) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index ff3692e597..bda55a80ed 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -28,11 +28,8 @@ from typing import Any, cast import numpy as np -import numpy.typing as npt -from pyvista.core.pointset import UnstructuredGrid from ansys.api.acp.v0 import ( - base_pb2, boolean_selection_rule_pb2_grpc, cad_geometry_pb2_grpc, cutoff_selection_rule_pb2_grpc, @@ -49,7 +46,6 @@ lookup_table_3d_pb2_grpc, material_pb2, material_pb2_grpc, - mesh_query_pb2_grpc, model_pb2, model_pb2_grpc, modeling_group_pb2_grpc, @@ -72,11 +68,17 @@ from ansys.api.acp.v0.base_pb2 import CollectionPath from .._typing_helper import PATH as _PATH -from .._utils.array_conversions import to_numpy from .._utils.path_to_str import path_to_str_checked from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty from .._utils.resource_paths import join as rp_join -from .._utils.visualization import to_pyvista_faces, to_pyvista_types +from ._elemental_or_nodal_data import ( + ElementalData, + NodalData, + ScalarData, + VectorData, + elemental_data_property, + nodal_data_property, +) from ._grpc_helpers.enum_wrapper import wrap_to_string_enum from ._grpc_helpers.exceptions import wrap_grpc_errors from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping @@ -89,14 +91,7 @@ ) from ._grpc_helpers.protocols import ObjectInfo from ._grpc_helpers.supported_since import supported_since -from ._mesh_data import ( - ElementalData, - NodalData, - ScalarData, - VectorData, - elemental_data_property, - nodal_data_property, -) +from ._mesh_data import full_mesh_property, shell_mesh_property, solid_mesh_property from .base import ServerWrapper, TreeObject from .boolean_selection_rule import BooleanSelectionRule from .cad_geometry import CADGeometry @@ -186,30 +181,6 @@ ) -@dataclasses.dataclass -class MeshData: - """Container for the mesh data of an ACP Model.""" - - node_labels: npt.NDArray[np.int32] - node_coordinates: npt.NDArray[np.float64] - element_labels: npt.NDArray[np.int32] - element_types: npt.NDArray[np.int32] - element_nodes: npt.NDArray[np.int32] - element_nodes_offsets: npt.NDArray[np.int32] - - def to_pyvista(self) -> UnstructuredGrid: - """Convert the mesh data to a PyVista mesh.""" - return UnstructuredGrid( - to_pyvista_faces( - element_types=self.element_types, - element_nodes=self.element_nodes, - element_nodes_offsets=self.element_nodes_offsets, - ), - to_pyvista_types(self.element_types), - self.node_coordinates, - ) - - @dataclasses.dataclass class ModelElementalData(ElementalData): """Represents elemental data for a Model.""" @@ -1002,19 +973,8 @@ def export_modeling_ply_geometries( FieldDefinition, field_definition_pb2_grpc.ObjectServiceStub ) - @property - def mesh(self) -> MeshData: - """Mesh on which the model is defined.""" - mesh_query_stub = mesh_query_pb2_grpc.MeshQueryServiceStub(self._channel) - reply = mesh_query_stub.GetMeshData(base_pb2.GetRequest(resource_path=self._resource_path)) - return MeshData( - node_labels=to_numpy(reply.node_labels), - node_coordinates=to_numpy(reply.node_coordinates), - element_labels=to_numpy(reply.element_labels), - element_types=to_numpy(reply.element_types), - element_nodes=to_numpy(reply.element_nodes), - element_nodes_offsets=to_numpy(reply.element_nodes_offsets), - ) - + mesh = full_mesh_property + shell_mesh = shell_mesh_property + solid_mesh = solid_mesh_property elemental_data = elemental_data_property(ModelElementalData) nodal_data = nodal_data_property(ModelNodalData) diff --git a/src/ansys/acp/core/_tree_objects/modeling_group.py b/src/ansys/acp/core/_tree_objects/modeling_group.py index aa0739f4aa..35a95d672a 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_group.py +++ b/src/ansys/acp/core/_tree_objects/modeling_group.py @@ -33,15 +33,16 @@ modeling_ply_pb2_grpc, ) -from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping -from ._grpc_helpers.property_helper import mark_grpc_properties -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.mapping import define_create_method, define_mutable_mapping +from ._grpc_helpers.property_helper import mark_grpc_properties +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .butt_joint_sequence import ButtJointSequence from .interface_layer import InterfaceLayer @@ -114,5 +115,8 @@ def _create_stub(self) -> modeling_group_pb2_grpc.ObjectServiceStub: ButtJointSequence, butt_joint_sequence_pb2_grpc.ObjectServiceStub ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property + elemental_data = elemental_data_property(ModelingGroupElementalData) nodal_data = nodal_data_property(ModelingGroupNodalData) diff --git a/src/ansys/acp/core/_tree_objects/modeling_ply.py b/src/ansys/acp/core/_tree_objects/modeling_ply.py index abc7eaaed2..6883f5eabe 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/modeling_ply.py @@ -33,6 +33,14 @@ from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadWriteProperty +from ._elemental_or_nodal_data import ( + ElementalData, + NodalData, + ScalarData, + VectorData, + elemental_data_property, + nodal_data_property, +) from ._grpc_helpers.edge_property_list import ( GenericEdgePropertyType, define_add_method, @@ -47,14 +55,7 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ( - ElementalData, - NodalData, - ScalarData, - VectorData, - elemental_data_property, - nodal_data_property, -) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .enums import ( @@ -449,5 +450,8 @@ def _create_stub(self) -> modeling_ply_pb2_grpc.ObjectServiceStub: ProductionPly, production_ply_pb2_grpc.ObjectServiceStub ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property + elemental_data = elemental_data_property(ModelingPlyElementalData) nodal_data = nodal_data_property(ModelingPlyNodalData) diff --git a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py index 28994688da..6822a23ab5 100644 --- a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py +++ b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py @@ -30,6 +30,13 @@ from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadWriteProperty +from ._elemental_or_nodal_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from ._grpc_helpers.linked_object_list import ( define_linked_object_list, define_polymorphic_linked_object_list, @@ -40,13 +47,7 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ( - ElementalData, - NodalData, - VectorData, - elemental_data_property, - nodal_data_property, -) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .boolean_selection_rule import BooleanSelectionRule from .cylindrical_selection_rule import CylindricalSelectionRule @@ -69,11 +70,6 @@ from .tube_selection_rule import TubeSelectionRule from .variable_offset_selection_rule import VariableOffsetSelectionRule -# Workaround: these imports are needed to make sphinx_autodoc_typehints understand -# the inherited members of the Elemental- and NodalData classes. -import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip - __all__ = [ "OrientedSelectionSet", "OrientedSelectionSetElementalData", @@ -278,5 +274,8 @@ def _create_stub(self) -> oriented_selection_set_pb2_grpc.ObjectServiceStub: allowed_types=(LookUpTable3DColumn, LookUpTable1DColumn), ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property + elemental_data = elemental_data_property(OrientedSelectionSetElementalData) nodal_data = nodal_data_property(OrientedSelectionSetNodalData) diff --git a/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py b/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py index 656031bc6f..2b625d96bd 100644 --- a/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py @@ -29,19 +29,20 @@ from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadWriteProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - grpc_link_property, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -50,7 +51,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "ParallelSelectionRule", @@ -152,5 +153,9 @@ def _create_stub(self) -> parallel_selection_rule_pb2_grpc.ObjectServiceStub: ) include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data + elemental_data = elemental_data_property(ParallelSelectionRuleElementalData) nodal_data = nodal_data_property(ParallelSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/production_ply.py b/src/ansys/acp/core/_tree_objects/production_ply.py index 6101cb1828..283c639a74 100644 --- a/src/ansys/acp/core/_tree_objects/production_ply.py +++ b/src/ansys/acp/core/_tree_objects/production_ply.py @@ -30,13 +30,7 @@ from ansys.api.acp.v0 import analysis_ply_pb2_grpc, production_ply_pb2, production_ply_pb2_grpc from .._utils.property_protocols import ReadOnlyProperty -from ._grpc_helpers.mapping import get_read_only_collection_property -from ._grpc_helpers.property_helper import ( - grpc_data_property_read_only, - grpc_link_property_read_only, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, ScalarData, @@ -44,6 +38,13 @@ elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.mapping import get_read_only_collection_property +from ._grpc_helpers.property_helper import ( + grpc_data_property_read_only, + grpc_link_property_read_only, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .analysis_ply import AnalysisPly from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb @@ -115,6 +116,10 @@ def _create_stub(self) -> production_ply_pb2_grpc.ObjectServiceStub: material = grpc_link_property_read_only("properties.material") angle: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.angle") thickness: ReadOnlyProperty[float] = grpc_data_property_read_only("properties.thickness") + + mesh = full_mesh_property + shell_mesh = shell_mesh_property + elemental_data = elemental_data_property(ProductionPlyElementalData) nodal_data = nodal_data_property(ProductionPlyNodalData) diff --git a/src/ansys/acp/core/_tree_objects/solid_element_set.py b/src/ansys/acp/core/_tree_objects/solid_element_set.py index 64186c5eff..cafbf1d6c2 100644 --- a/src/ansys/acp/core/_tree_objects/solid_element_set.py +++ b/src/ansys/acp/core/_tree_objects/solid_element_set.py @@ -29,8 +29,8 @@ from .._utils.array_conversions import to_tuple_from_1D_array from .._utils.property_protocols import ReadOnlyProperty +from ._elemental_or_nodal_data import ElementalData, NodalData from ._grpc_helpers.property_helper import grpc_data_property_read_only, mark_grpc_properties -from ._mesh_data import ElementalData, NodalData from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb from .object_registry import register diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index 72fc4eb8c6..3e279efe4f 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -36,6 +36,13 @@ ) from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from ._elemental_or_nodal_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from ._grpc_helpers.linked_object_list import ( define_linked_object_list, define_polymorphic_linked_object_list, @@ -51,13 +58,6 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ( - ElementalData, - NodalData, - VectorData, - elemental_data_property, - nodal_data_property, -) from ._solid_model_export import SolidModelExportMixin from .base import ( CreatableTreeObject, diff --git a/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py b/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py index 3a78b22e11..35a35173ab 100644 --- a/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py @@ -29,19 +29,20 @@ from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadWriteProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - grpc_link_property, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -50,7 +51,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "SphericalSelectionRule", @@ -143,5 +144,8 @@ def _create_stub(self) -> spherical_selection_rule_pb2_grpc.ObjectServiceStub: ) include_rule: ReadWriteProperty[bool, bool] = grpc_data_property("properties.include_rule_type") + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data elemental_data = elemental_data_property(SphericalSelectionRuleElementalData) nodal_data = nodal_data_property(SphericalSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/tube_selection_rule.py b/src/ansys/acp/core/_tree_objects/tube_selection_rule.py index 17f90ac91d..37c9da4e3a 100644 --- a/src/ansys/acp/core/_tree_objects/tube_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/tube_selection_rule.py @@ -29,19 +29,20 @@ from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadWriteProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - grpc_link_property, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .enums import status_type_from_pb @@ -50,7 +51,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "TubeSelectionRule", @@ -159,5 +160,8 @@ def _create_stub(self) -> tube_selection_rule_pb2_grpc.ObjectServiceStub: "properties.tail_extension" ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data elemental_data = elemental_data_property(TubeSelectionRuleElementalData) nodal_data = nodal_data_property(TubeSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py b/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py index 3830cb8c9f..b81c14fa8f 100644 --- a/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/variable_offset_selection_rule.py @@ -32,19 +32,20 @@ from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array from .._utils.property_protocols import ReadWriteProperty -from ._grpc_helpers.property_helper import ( - grpc_data_property, - grpc_data_property_read_only, - grpc_link_property, - mark_grpc_properties, -) -from ._mesh_data import ( +from ._elemental_or_nodal_data import ( ElementalData, NodalData, VectorData, elemental_data_property, nodal_data_property, ) +from ._grpc_helpers.property_helper import ( + grpc_data_property, + grpc_data_property_read_only, + grpc_link_property, + mark_grpc_properties, +) +from ._mesh_data import full_mesh_property, shell_mesh_property from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .element_set import ElementSet @@ -55,7 +56,7 @@ # Workaround: these imports are needed to make sphinx_autodoc_typehints understand # the inherited members of the Elemental- and NodalData classes. import numpy as np # noqa: F401 isort:skip -from ._mesh_data import ScalarData # noqa: F401 isort:skip +from ._elemental_or_nodal_data import ScalarData # noqa: F401 isort:skip __all__ = [ "VariableOffsetSelectionRule", @@ -175,5 +176,8 @@ def _create_stub(self) -> variable_offset_selection_rule_pb2_grpc.ObjectServiceS "properties.distance_along_edge" ) + mesh = full_mesh_property + shell_mesh = shell_mesh_property + # selection rules don't have solid mesh data elemental_data = elemental_data_property(VariableOffsetSelectionRuleElementalData) nodal_data = nodal_data_property(VariableOffsetSelectionRuleNodalData) diff --git a/tests/unittests/test_analysis_ply.py b/tests/unittests/test_analysis_ply.py index bb939c5a51..48bd919c76 100644 --- a/tests/unittests/test_analysis_ply.py +++ b/tests/unittests/test_analysis_ply.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from numpy.testing import assert_equal import pytest from ansys.acp.core import AnalysisPlyElementalData, AnalysisPlyNodalData, FabricWithAngle, Model @@ -149,3 +150,27 @@ def test_mesh_data_existence(model: Model): assert isinstance(elemental_data, AnalysisPlyElementalData) nodal_data = analysis_ply.nodal_data assert isinstance(nodal_data, AnalysisPlyNodalData) + + +def test_meshes(model: Model, raises_before_version): + """ + Test that the mesh properties can be retrieved. + """ + analysis_ply = list(get_first_production_ply(model).analysis_plies.values())[0] + with raises_before_version("25.1"): + assert_equal(analysis_ply.mesh.element_labels, [1]) + with raises_before_version("25.1"): + assert_equal(analysis_ply.shell_mesh.element_labels, [1]) + with raises_before_version("25.1"): + assert_equal(analysis_ply.solid_mesh.element_labels, []) + with raises_before_version("25.1"): + model.create_solid_model( + element_sets=[model.element_sets["All_Elements"]], + ) + model.update() + with raises_before_version("25.1"): + assert_equal(analysis_ply.mesh.element_labels, [1, 2]) + with raises_before_version("25.1"): + assert_equal(analysis_ply.shell_mesh.element_labels, [1]) + with raises_before_version("25.1"): + assert_equal(analysis_ply.solid_mesh.element_labels, [2]) From 3b68c1368e888cb3c1f9d617ff65a6730ddaca3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 08:13:16 +0100 Subject: [PATCH 58/96] Bump codecov/codecov-action from 4 to 5 (#671) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index c49b035b4c..0edbe49f3d 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -191,7 +191,7 @@ jobs: IMAGE_NAME: ghcr.io/ansys/acp:${{ matrix.server-version }} - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: From 699324d2a73ba0886195ac3f79f73cc66429f843 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 07:26:27 +0000 Subject: [PATCH 59/96] Bump the dependencies group with 3 updates (#672) Bumps the dependencies group with 3 updates: [ansys-tools-path](https://github.com/ansys/ansys-tools-path), [ansys-sphinx-theme](https://github.com/ansys/ansys-sphinx-theme) and [hypothesis](https://github.com/HypothesisWorks/hypothesis). Updates `ansys-tools-path` from 0.6.0 to 0.7.0 - [Release notes](https://github.com/ansys/ansys-tools-path/releases) - [Changelog](https://github.com/ansys/ansys-tools-path/blob/main/CHANGELOG.md) - [Commits](https://github.com/ansys/ansys-tools-path/compare/v0.6.0...v0.7.0) Updates `ansys-sphinx-theme` from 1.2.0 to 1.2.1 - [Release notes](https://github.com/ansys/ansys-sphinx-theme/releases) - [Commits](https://github.com/ansys/ansys-sphinx-theme/compare/v1.2.0...v1.2.1) Updates `hypothesis` from 6.118.7 to 6.119.3 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.118.7...hypothesis-python-6.119.3) --- updated-dependencies: - dependency-name: ansys-tools-path dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: ansys-sphinx-theme dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dominik Gresch --- poetry.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6742417968..23e1f83975 100644 --- a/poetry.lock +++ b/poetry.lock @@ -489,13 +489,13 @@ clr-loader = ">=0.2.5,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.2.0" +version = "1.2.1" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.10" files = [ - {file = "ansys_sphinx_theme-1.2.0-py3-none-any.whl", hash = "sha256:2af3824e058762f9d8c73c49082fcd6d79d3961f8949bf24ee27d3a71ca0e957"}, - {file = "ansys_sphinx_theme-1.2.0.tar.gz", hash = "sha256:1d296945ebfaeb478f7a48967c5de6741b3f762f04bd968ea2e20727d53dc63f"}, + {file = "ansys_sphinx_theme-1.2.1-py3-none-any.whl", hash = "sha256:b133ce7b94ef50c6043ee088fe32ed222774eb0af178fc5c07c960941c027360"}, + {file = "ansys_sphinx_theme-1.2.1.tar.gz", hash = "sha256:9eacd4a242ca1747b61e930b6e134f4659b198ac61bc4290ccd0c461cbae5549"}, ] [package.dependencies] @@ -544,13 +544,13 @@ grpcio-health-checking = ">=1.43,<2.0" [[package]] name = "ansys-tools-path" -version = "0.6.0" +version = "0.7.0" description = "Library to locate Ansys products in a local machine." optional = false -python-versions = "<4,>=3.8" +python-versions = "<4,>=3.10" files = [ - {file = "ansys_tools_path-0.6.0-py3-none-any.whl", hash = "sha256:b0ffe61fece59dbcae63023b8c39c4fc6bf722d993bb70626cb9fd087ff28266"}, - {file = "ansys_tools_path-0.6.0.tar.gz", hash = "sha256:a6a3a35b2700905319347c785b59a7ac12e6f60148b3b516ec09546463fe9c8f"}, + {file = "ansys_tools_path-0.7.0-py3-none-any.whl", hash = "sha256:970596c1d9375498b97fa602c3f7e56435ca84e147743809b6a597667c654c57"}, + {file = "ansys_tools_path-0.7.0.tar.gz", hash = "sha256:96663cd13436e20f2ab2347aa6c487890962319c80bebc181419c957c19a8de0"}, ] [package.dependencies] @@ -558,9 +558,9 @@ click = ">=8.1.3" platformdirs = ">=3.6.0" [package.extras] -build = ["build (==1.2.1)", "twine (==5.1.0)"] -doc = ["Sphinx (==7.3.7)", "ansys-sphinx-theme (==0.16.2)", "numpydoc (==1.7.0)", "sphinx-copybutton (==0.5.2)"] -tests = ["pyfakefs (==5.5.0)", "pytest (==8.2.1)", "pytest-cov (==5.0.0)"] +build = ["build (==1.2.2.post1)", "twine (==5.1.1)"] +doc = ["Sphinx (==8.1.3)", "ansys-sphinx-theme (==1.2.0)", "numpydoc (==1.8.0)", "sphinx-copybutton (==0.5.2)"] +tests = ["pyfakefs (==5.7.1)", "pytest (==8.3.3)", "pytest-cov (==6.0.0)"] [[package]] name = "ansys-tools-visualization-interface" @@ -1904,13 +1904,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.118.7" +version = "6.119.3" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.118.7-py3-none-any.whl", hash = "sha256:5fe1d80f46d81c6160ef762e4e11a61bb4eb6838a8fb7bd3c5a2542fb107bc38"}, - {file = "hypothesis-6.118.7.tar.gz", hash = "sha256:604328f5d766a056182f54b4826f9b2d5f664f42bff68fd81b4d9d6c44b2398b"}, + {file = "hypothesis-6.119.3-py3-none-any.whl", hash = "sha256:dff13689c4ceb0d84d92e0309fca3ccc1b547ac30552037c712a9080eb75cd05"}, + {file = "hypothesis-6.119.3.tar.gz", hash = "sha256:1403676d95bc9f118a30ce2c97fcbdd28dd99f3a1ffe3456970d98a56b370f36"}, ] [package.dependencies] From 75168cbc83dc74d61291e5f9d1e56ffe5ab132e3 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 20 Nov 2024 09:22:03 +0100 Subject: [PATCH 60/96] Benchmark: fix and re-enable 100ms delay case (#674) Fix the 100ms delay benchmark case, by increasing the timeout when checking whether the server is still running from 1 to 2 seconds. Closes #634. --- src/ansys/acp/core/_server/acp_instance.py | 8 ++++---- tests/benchmarks/conftest.py | 5 +---- tests/conftest.py | 5 +++-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ansys/acp/core/_server/acp_instance.py b/src/ansys/acp/core/_server/acp_instance.py index e1c1ef23f4..a520ee38ed 100644 --- a/src/ansys/acp/core/_server/acp_instance.py +++ b/src/ansys/acp/core/_server/acp_instance.py @@ -198,10 +198,10 @@ def clear(self) -> None: saving them to a file. """ model_stub = model_pb2_grpc.ObjectServiceStub(self._channel) - for model in model_stub.List( - ListRequest(collection_path=CollectionPath(value=Model._COLLECTION_LABEL)) - ).objects: - with wrap_grpc_errors(): + with wrap_grpc_errors(): + for model in model_stub.List( + ListRequest(collection_path=CollectionPath(value=Model._COLLECTION_LABEL)) + ).objects: model_stub.Delete( DeleteRequest( resource_path=model.info.resource_path, version=model.info.version diff --git a/tests/benchmarks/conftest.py b/tests/benchmarks/conftest.py index 1056be8b65..a1ec388173 100644 --- a/tests/benchmarks/conftest.py +++ b/tests/benchmarks/conftest.py @@ -111,10 +111,7 @@ def launcher_configuration(request): ServerNetworkOptions(delay_ms=0, rate_kbit=1e6), ServerNetworkOptions(delay_ms=1, rate_kbit=1e6), ServerNetworkOptions(delay_ms=10, rate_kbit=1e6), - # Currently disabled since the server fails to start correctly. - # See https://github.com/ansys/pyacp/issues/634 - # - # ServerNetworkOptions(delay_ms=100, rate_kbit=1e6), + ServerNetworkOptions(delay_ms=100, rate_kbit=1e6), ServerNetworkOptions(delay_ms=0, rate_kbit=1e4), ServerNetworkOptions(delay_ms=0, rate_kbit=1e3), ServerNetworkOptions(delay_ms=0, rate_kbit=1e2), diff --git a/tests/conftest.py b/tests/conftest.py index 2bcfad19a3..9e3c8b52c7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -82,7 +82,8 @@ BUILD_BENCHMARK_IMAGE_OPTION_KEY = "--build-benchmark-image" VALIDATE_BENCHMARKS_ONLY_OPTION_KEY = "--validate-benchmarks-only" SERVER_STARTUP_TIMEOUT = 30.0 -SERVER_STOP_TIMEOUT = 1.0 +SERVER_STOP_TIMEOUT = 2.0 +SERVER_CHECK_TIMEOUT = 2.0 pytest.register_assert_rewrite("common") @@ -219,7 +220,7 @@ def check_grpc_server_before_run( ) -> Generator[None, None, None]: """Check if the server still responds before running each test, otherwise restart it.""" try: - acp_instance.wait(timeout=1.0) + acp_instance.wait(timeout=SERVER_CHECK_TIMEOUT) except RuntimeError: acp_instance.restart(stop_timeout=SERVER_STOP_TIMEOUT) acp_instance.wait(timeout=SERVER_STARTUP_TIMEOUT) From acffc13e073f939011231e5f638f835097a887a1 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 20 Nov 2024 09:34:52 +0100 Subject: [PATCH 61/96] Show which examples use an object in the API documentation (#683) Use the 'minigallery' feature of `sphinx-gallery` to show which examples use a specific feature in its API documentation. Note that examples which are not rendered (i.e., the PyMechanical ones) do not show up. Also, uses within a function (defined in the example) do not seem to be captured. However, where present, I think it's a nice way of linking the API reference to a usage example. When there are no examples, the section is not shown. --- .gitignore | 2 +- doc/make.bat | 2 +- doc/source/_templates/autosummary/base.rst | 12 ++++++ doc/source/_templates/autosummary/class.rst | 39 +++++++++++++++++++ .../autosummary/experimental/base.rst.jinja2 | 14 +++++++ .../no_methods_doc/class.rst.jinja2 | 6 +++ doc/source/conf.py | 7 ++-- 7 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 doc/source/_templates/autosummary/base.rst create mode 100644 doc/source/_templates/autosummary/class.rst create mode 100644 doc/source/_templates/autosummary/experimental/base.rst.jinja2 diff --git a/.gitignore b/.gitignore index 26bdfceb32..aaabf9661b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ dist/ # autogenerated docs _autosummary - +_gallery_backreferences # Testing .coverage diff --git a/doc/make.bat b/doc/make.bat index 16ca610eb2..cc0f85c6bb 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -34,7 +34,7 @@ goto end :clean rmdir /s /q %BUILDDIR% > /NUL 2>&1 -for /d /r %SOURCEDIR% %%d in (_autosummary) do @if exist "%%d" rmdir /s /q "%%d" +for /d /r %SOURCEDIR% %%d in (_autosummary,_gallery_backreferences) do @if exist "%%d" rmdir /s /q "%%d" rmdir /s /q %SOURCEDIR%\examples\gallery_examples goto end diff --git a/doc/source/_templates/autosummary/base.rst b/doc/source/_templates/autosummary/base.rst new file mode 100644 index 0000000000..80e4862b95 --- /dev/null +++ b/doc/source/_templates/autosummary/base.rst @@ -0,0 +1,12 @@ +.. vale off + +{{ name | escape | underline}} + +.. currentmodule:: {{ module }} + +.. auto{{ objtype }}:: {{ objname }} + +.. minigallery:: + :add-heading: Examples using {{ objname }} + + {{ module }}.{{ objname }} diff --git a/doc/source/_templates/autosummary/class.rst b/doc/source/_templates/autosummary/class.rst new file mode 100644 index 0000000000..bb5752170c --- /dev/null +++ b/doc/source/_templates/autosummary/class.rst @@ -0,0 +1,39 @@ +.. vale off + +{{ objname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + + {% block methods %} + + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + :toctree: + {% for item in methods %} + {% if item != "__init__" %} + {{ name }}.{{ item }} + {% endif %} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + :toctree: + {% for item in attributes %} + {{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +.. minigallery:: + :add-heading: Examples using {{ objname }} + + {{ module }}.{{ objname }} diff --git a/doc/source/_templates/autosummary/experimental/base.rst.jinja2 b/doc/source/_templates/autosummary/experimental/base.rst.jinja2 new file mode 100644 index 0000000000..31470b81d1 --- /dev/null +++ b/doc/source/_templates/autosummary/experimental/base.rst.jinja2 @@ -0,0 +1,14 @@ +{{ name | escape | underline}} + +.. currentmodule:: {{ module }} + +.. warning:: + + This is an experimental feature. The API is subject to change without notice. + +.. auto{{ objtype }}:: {{ objname }} + +.. minigallery:: + :add-heading: Examples using {{ objname }} + + {{ module }}.{{ objname }} diff --git a/doc/source/_templates/autosummary/no_methods_doc/class.rst.jinja2 b/doc/source/_templates/autosummary/no_methods_doc/class.rst.jinja2 index ed6ca024ed..610ab08085 100644 --- a/doc/source/_templates/autosummary/no_methods_doc/class.rst.jinja2 +++ b/doc/source/_templates/autosummary/no_methods_doc/class.rst.jinja2 @@ -17,3 +17,9 @@ {%- endfor %} {% endif %} {% endblock %} + + +.. minigallery:: + :add-heading: Examples using {{ objname }} + + {{ module }}.{{ objname }} diff --git a/doc/source/conf.py b/doc/source/conf.py index b63d08e37c..19264b1540 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -275,9 +275,10 @@ def _signature( # Sort gallery example by filename instead of number of lines (default) "within_subsection_order": "FileNameSortKey", # directory where function granular galleries are stored - "backreferences_dir": None, - # Modules for which function level galleries are created. In - "doc_module": "ansys-acp-core", + "backreferences_dir": "api/_gallery_backreferences", + # Modules for which function level galleries are created. + "doc_module": ("ansys.acp.core"), + "exclude_implicit_doc": {"ansys\\.acp\\.core\\._.*"}, # ignore private submodules "image_scrapers": (DynamicScraper(), "matplotlib"), "ignore_pattern": r"__init__\.py", "thumbnail_size": (350, 350), From 6da89433e586c8ea8a3dac2d057fe86d1f04d621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:50:41 +0100 Subject: [PATCH 62/96] Clean up of core module: part 1 (#675) * rename ACP to ACPInstance and move example_helpers.py to extras * update example files (new import) * rename PORT_PYACP to PORT_ACP and fix missed renaming of image_name_pyacp to image_name_acp * fix docu * skip __init.py of extras from code coverage --------- Co-authored-by: Dominik Gresch --- .github/workflows/ci_cd.yml | 4 +-- doc/source/api/example_helpers.rst | 2 +- doc/source/api/server.rst | 2 +- .../user_guide/howto/launch_configuration.rst | 2 +- .../user_guide/howto/visualize_model.rst | 10 +++---- .../user_guide/security_considerations.rst | 2 +- docker-compose/docker-compose-benchmark.yaml | 4 +-- examples/001_basic_flat_plate.py | 6 ++-- examples/002_sandwich_panel.py | 4 +-- examples/003_basic_rules.py | 2 +- examples/004_advanced_rules.py | 11 ++----- examples/005_thickness_definitions.py | 8 ++--- examples/006_rosettes.py | 2 +- examples/007_direction_definitions.py | 2 +- examples/009_optimization.py | 5 ++-- src/ansys/acp/core/__init__.py | 7 ++--- src/ansys/acp/core/_server/__init__.py | 4 +-- src/ansys/acp/core/_server/acp_instance.py | 4 +-- .../acp/core/_server/docker-compose.yaml | 4 +-- src/ansys/acp/core/_server/docker_compose.py | 6 ++-- src/ansys/acp/core/_server/launch.py | 6 ++-- src/ansys/acp/core/_tree_objects/base.py | 4 +-- src/ansys/acp/core/_workflow.py | 14 ++++----- src/ansys/acp/core/extras/__init__.py | 29 +++++++++++++++++++ .../acp/core/{ => extras}/example_helpers.py | 0 tests/benchmarks/conftest.py | 2 +- tests/conftest.py | 8 ++--- 27 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 src/ansys/acp/core/extras/__init__.py rename src/ansys/acp/core/{ => extras}/example_helpers.py (100%) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 0edbe49f3d..ee75a4659f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -278,7 +278,7 @@ jobs: run: > poetry run ansys-launcher configure ACP docker_compose - --image_name_pyacp=${{ env.DOCKER_IMAGE_NAME }} + --image_name_acp=${{ env.DOCKER_IMAGE_NAME }} --image_name_filetransfer=ghcr.io/ansys/tools-filetransfer:latest --license_server=1055@$LICENSE_SERVER --keep_volume=False @@ -361,7 +361,7 @@ jobs: run: > poetry run ansys-launcher configure ACP docker_compose - --image_name_pyacp=${{ env.DOCKER_IMAGE_NAME }} + --image_name_acp=${{ env.DOCKER_IMAGE_NAME }} --image_name_filetransfer=ghcr.io/ansys/tools-filetransfer:latest --license_server=1055@$LICENSE_SERVER --keep_volume=False diff --git a/doc/source/api/example_helpers.rst b/doc/source/api/example_helpers.rst index 7ddab31ee5..fdd6df5bbd 100644 --- a/doc/source/api/example_helpers.rst +++ b/doc/source/api/example_helpers.rst @@ -1,7 +1,7 @@ Example helpers --------------- -.. currentmodule:: ansys.acp.core.example_helpers +.. currentmodule:: ansys.acp.core.extras.example_helpers .. autosummary:: :toctree: _autosummary diff --git a/doc/source/api/server.rst b/doc/source/api/server.rst index d9107d0285..f6903c7291 100644 --- a/doc/source/api/server.rst +++ b/doc/source/api/server.rst @@ -6,7 +6,7 @@ Server management .. autosummary:: :toctree: _autosummary - ACP + ACPInstance ConnectLaunchConfig DirectLaunchConfig DockerComposeLaunchConfig diff --git a/doc/source/user_guide/howto/launch_configuration.rst b/doc/source/user_guide/howto/launch_configuration.rst index 060f82c233..3f2e82e4eb 100644 --- a/doc/source/user_guide/howto/launch_configuration.rst +++ b/doc/source/user_guide/howto/launch_configuration.rst @@ -94,7 +94,7 @@ This parameter expects a configuration object matching the selected ``launch_mod acp = pyacp.launch_acp( config=pyacp.DockerComposeLaunchConfig( - image_name_pyacp="ghcr.io/ansys/acp:latest", + image_name_acp="ghcr.io/ansys/acp:latest", image_name_filetransfer="ghcr.io/ansys/tools-filetransfer:latest", keep_volume=True, license_server=f"1055@{os.environ['LICENSE_SERVER']}", diff --git a/doc/source/user_guide/howto/visualize_model.rst b/doc/source/user_guide/howto/visualize_model.rst index dd357c9640..9c0c4b6017 100644 --- a/doc/source/user_guide/howto/visualize_model.rst +++ b/doc/source/user_guide/howto/visualize_model.rst @@ -13,18 +13,18 @@ Visualize model >>> import tempfile >>> import pathlib >>> import ansys.acp.core as pyacp - >>> from ansys.acp.core import example_helpers + >>> from ansys.acp.core.extras import ExampleKeys, get_example_file >>> acp = pyacp.launch_acp() >>> tempdir = tempfile.TemporaryDirectory() - >>> input_file = example_helpers.get_example_file( - ... example_helpers.ExampleKeys.RACE_CAR_NOSE_ACPH5, pathlib.Path(tempdir.name) + >>> input_file = get_example_file( + ... ExampleKeys.RACE_CAR_NOSE_ACPH5, pathlib.Path(tempdir.name) ... ) >>> path = acp.upload_file(input_file) >>> model = acp.import_model(path=path) - >>> input_file_geometry = example_helpers.get_example_file( - ... example_helpers.ExampleKeys.RACE_CAR_NOSE_STEP, pathlib.Path(tempdir.name) + >>> input_file_geometry = get_example_file( + ... ExampleKeys.RACE_CAR_NOSE_STEP, pathlib.Path(tempdir.name) ... ) >>> path_geometry = acp.upload_file(input_file_geometry) >>> model.create_cad_geometry(name="nose_geometry", external_path=path_geometry) diff --git a/doc/source/user_guide/security_considerations.rst b/doc/source/user_guide/security_considerations.rst index 366c7c327e..220b32ab3d 100644 --- a/doc/source/user_guide/security_considerations.rst +++ b/doc/source/user_guide/security_considerations.rst @@ -60,7 +60,7 @@ system. File up- and downloads ---------------------- -The :py:meth:`.ACP.upload_file` and :py:meth:`.ACP.download_file` methods create files +The :py:meth:`.ACPInstance.upload_file` and :py:meth:`.ACPInstance.download_file` methods create files on the local or remote machine, without any validation of the file content or path. When exposing these methods to untrusted users, it is important to validate that diff --git a/docker-compose/docker-compose-benchmark.yaml b/docker-compose/docker-compose-benchmark.yaml index 5d499d1630..ffcfc8b854 100644 --- a/docker-compose/docker-compose-benchmark.yaml +++ b/docker-compose/docker-compose-benchmark.yaml @@ -5,7 +5,7 @@ services: # This docker-compose file should be used only with the container generated from # 'ConnectionTest.Dockerfile', since that image switches from the 'root' user # to the 'container' user in the ENTRYPOINT script. - image: ${IMAGE_NAME_PYACP:-pyacp-benchmark-runner} + image: ${IMAGE_NAME_ACP:-pyacp-benchmark-runner} command: [ "${PYACP_DELAY:-100ms}", @@ -15,7 +15,7 @@ services: environment: - ANSYSLMD_LICENSE_FILE=${ANSYSLMD_LICENSE_FILE} ports: - - "${PORT_PYACP:-50555}:50051" + - "${PORT_ACP:-50555}:50051" working_dir: /home/container/workdir volumes: - "acp_data:/home/container/workdir/" diff --git a/examples/001_basic_flat_plate.py b/examples/001_basic_flat_plate.py index f1adb50b8a..9c21421df8 100644 --- a/examples/001_basic_flat_plate.py +++ b/examples/001_basic_flat_plate.py @@ -54,7 +54,6 @@ from ansys.acp.core import ( ACPWorkflow, PlyType, - example_helpers, get_composite_post_processing_files, get_directions_plotter, get_dpf_unit_system, @@ -62,6 +61,7 @@ material_property_sets, print_model, ) +from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 3 @@ -73,9 +73,7 @@ # Get the example file from the server. tempdir = tempfile.TemporaryDirectory() WORKING_DIR = pathlib.Path(tempdir.name) -input_file = example_helpers.get_example_file( - example_helpers.ExampleKeys.BASIC_FLAT_PLATE_DAT, WORKING_DIR -) +input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_DAT, WORKING_DIR) # %% # Launch the PyACP server and connect to it. diff --git a/examples/002_sandwich_panel.py b/examples/002_sandwich_panel.py index e4c7c3cd0e..324731bdb5 100644 --- a/examples/002_sandwich_panel.py +++ b/examples/002_sandwich_panel.py @@ -49,7 +49,7 @@ launch_acp, print_model, ) -from ansys.acp.core.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core.extras import ExampleKeys, get_example_file from ansys.acp.core.material_property_sets import ConstantEngineeringConstants, ConstantStrainLimits # sphinx_gallery_thumbnail_number = 2 @@ -210,7 +210,7 @@ model.update() print_model(workflow.model) # sphinx_gallery_start_ignore -from ansys.acp.core.example_helpers import _run_analysis +from ansys.acp.core.extras.example_helpers import _run_analysis # Run the analysis to ensure that all the material properties have been correctly # defined. diff --git a/examples/003_basic_rules.py b/examples/003_basic_rules.py index 42c4134193..269315ab1b 100644 --- a/examples/003_basic_rules.py +++ b/examples/003_basic_rules.py @@ -45,7 +45,7 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ACPWorkflow, LinkedSelectionRule, launch_acp -from ansys.acp.core.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = -1 diff --git a/examples/004_advanced_rules.py b/examples/004_advanced_rules.py index 38792f51b2..1314dfac37 100644 --- a/examples/004_advanced_rules.py +++ b/examples/004_advanced_rules.py @@ -55,10 +55,9 @@ DimensionType, EdgeSetType, LinkedSelectionRule, - example_helpers, launch_acp, ) -from ansys.acp.core.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 5 @@ -155,9 +154,7 @@ # %% # Add a CAD geometry to the model. -triangle_path = example_helpers.get_example_file( - example_helpers.ExampleKeys.RULE_GEOMETRY_TRIANGLE, WORKING_DIR -) +triangle_path = get_example_file(ExampleKeys.RULE_GEOMETRY_TRIANGLE, WORKING_DIR) triangle = workflow.add_cad_geometry_from_local_file(triangle_path) @@ -198,9 +195,7 @@ # %% # Add the cutoff CAD geometry to the model. -cutoff_plane_path = example_helpers.get_example_file( - example_helpers.ExampleKeys.CUT_OFF_GEOMETRY, WORKING_DIR -) +cutoff_plane_path = get_example_file(ExampleKeys.CUT_OFF_GEOMETRY, WORKING_DIR) cut_off_plane = workflow.add_cad_geometry_from_local_file(cutoff_plane_path) # Note: It is important to update the model here, because the root_shapes of the diff --git a/examples/005_thickness_definitions.py b/examples/005_thickness_definitions.py index bb10396fff..85698928c8 100644 --- a/examples/005_thickness_definitions.py +++ b/examples/005_thickness_definitions.py @@ -45,8 +45,8 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import ACPWorkflow, DimensionType, ThicknessType, example_helpers, launch_acp -from ansys.acp.core.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core import ACPWorkflow, DimensionType, ThicknessType, launch_acp +from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 2 @@ -93,9 +93,7 @@ # %% # Add the solid geometry to the model that defines the thickness. -thickness_geometry_file = example_helpers.get_example_file( - example_helpers.ExampleKeys.THICKNESS_GEOMETRY, WORKING_DIR -) +thickness_geometry_file = get_example_file(ExampleKeys.THICKNESS_GEOMETRY, WORKING_DIR) thickness_geometry = workflow.add_cad_geometry_from_local_file(thickness_geometry_file) # Note: It is important to update the model here, because the root_shapes of the diff --git a/examples/006_rosettes.py b/examples/006_rosettes.py index c36e245e89..0255728981 100644 --- a/examples/006_rosettes.py +++ b/examples/006_rosettes.py @@ -52,7 +52,7 @@ get_directions_plotter, launch_acp, ) -from ansys.acp.core.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 4 diff --git a/examples/007_direction_definitions.py b/examples/007_direction_definitions.py index f4a0084525..4102d9cbfd 100644 --- a/examples/007_direction_definitions.py +++ b/examples/007_direction_definitions.py @@ -53,7 +53,7 @@ get_directions_plotter, launch_acp, ) -from ansys.acp.core.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core.extras import ExampleKeys, get_example_file # %% # Start ACP and load the model diff --git a/examples/009_optimization.py b/examples/009_optimization.py index d4d2251d75..6d1f6525f4 100644 --- a/examples/009_optimization.py +++ b/examples/009_optimization.py @@ -61,6 +61,7 @@ # %% # Import Ansys libraries import ansys.acp.core as pyacp +from ansys.acp.core.extras import ExampleKeys, get_example_file import ansys.dpf.composites as pydpf_composites import ansys.mapdl.core as pymapdl @@ -97,8 +98,8 @@ # It returns a :class:`.ACPWorkflow` object that can be used to access the model and # generate the output files. -input_file = pyacp.example_helpers.get_example_file( - example_key=pyacp.example_helpers.ExampleKeys.OPTIMIZATION_EXAMPLE_DAT, +input_file = get_example_file( + example_key=ExampleKeys.OPTIMIZATION_EXAMPLE_DAT, working_directory=workdir, ) diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 340f719fdb..b61c0ba2b1 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -27,12 +27,12 @@ import importlib.metadata -from . import example_helpers, material_property_sets +from . import material_property_sets from ._model_printer import get_model_tree, print_model from ._plotter import get_directions_plotter from ._recursive_copy import LinkedObjectHandling, recursive_copy from ._server import ( - ACP, + ACPInstance, ConnectLaunchConfig, DirectLaunchConfig, DockerComposeLaunchConfig, @@ -195,7 +195,7 @@ __all__ = [ "__version__", - "ACP", + "ACPInstance", "ACPWorkflow", "AnalysisPly", "AnalysisPlyElementalData", @@ -236,7 +236,6 @@ "ElementSetElementalData", "ElementSetNodalData", "ElementTechnology", - "example_helpers", "ExportSettings", "ExtrusionGuide", "ExtrusionGuideType", diff --git a/src/ansys/acp/core/_server/__init__.py b/src/ansys/acp/core/_server/__init__.py index b67aa21117..f9a0ce487d 100644 --- a/src/ansys/acp/core/_server/__init__.py +++ b/src/ansys/acp/core/_server/__init__.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from .acp_instance import ACP +from .acp_instance import ACPInstance from .common import LaunchMode from .connect import ConnectLaunchConfig from .direct import DirectLaunchConfig @@ -28,7 +28,7 @@ from .launch import launch_acp __all__ = [ - "ACP", + "ACPInstance", "ConnectLaunchConfig", "DirectLaunchConfig", "DockerComposeLaunchConfig", diff --git a/src/ansys/acp/core/_server/acp_instance.py b/src/ansys/acp/core/_server/acp_instance.py index a520ee38ed..7db71a4ab0 100644 --- a/src/ansys/acp/core/_server/acp_instance.py +++ b/src/ansys/acp/core/_server/acp_instance.py @@ -39,7 +39,7 @@ from .._typing_helper import PATH as _PATH from .common import ServerProtocol -__all__ = ["ACP"] +__all__ = ["ACPInstance"] class FiletransferStrategy(Protocol): @@ -81,7 +81,7 @@ def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: ServerT = TypeVar("ServerT", bound=ServerProtocol, covariant=True) -class ACP(Generic[ServerT]): +class ACPInstance(Generic[ServerT]): """Control an ACP instance. Supports the following operations to control an ACP instance: diff --git a/src/ansys/acp/core/_server/docker-compose.yaml b/src/ansys/acp/core/_server/docker-compose.yaml index 48261b00c9..b9c511cb18 100644 --- a/src/ansys/acp/core/_server/docker-compose.yaml +++ b/src/ansys/acp/core/_server/docker-compose.yaml @@ -2,11 +2,11 @@ version: '3.8' services: acp-grpc-server: restart: unless-stopped - image: ${IMAGE_NAME_PYACP:-ghcr.io/ansys/acp:latest} + image: ${IMAGE_NAME_ACP:-ghcr.io/ansys/acp:latest} environment: - ANSYSLMD_LICENSE_FILE=${ANSYSLMD_LICENSE_FILE} ports: - - "${PORT_PYACP:-50555}:50051" + - "${PORT_ACP:-50555}:50051" working_dir: /home/container/workdir volumes: - "acp_data:/home/container/workdir/" diff --git a/src/ansys/acp/core/_server/docker_compose.py b/src/ansys/acp/core/_server/docker_compose.py index e531e1914a..47b195952d 100644 --- a/src/ansys/acp/core/_server/docker_compose.py +++ b/src/ansys/acp/core/_server/docker_compose.py @@ -63,7 +63,7 @@ def _get_default_license_server() -> str: class DockerComposeLaunchConfig: """Configuration options for launching ACP through docker compose.""" - image_name_pyacp: str = dataclasses.field( + image_name_acp: str = dataclasses.field( default="ghcr.io/ansys/acp:latest", metadata={METADATA_KEY_DOC: "Docker image running the ACP gRPC server."}, ) @@ -125,7 +125,7 @@ def __init__(self, *, config: DockerComposeLaunchConfig): self._env = copy.deepcopy(os.environ) self._env.update( - IMAGE_NAME_PYACP=config.image_name_pyacp, + IMAGE_NAME_ACP=config.image_name_acp, IMAGE_NAME_FILETRANSFER=config.image_name_filetransfer, ANSYSLMD_LICENSE_FILE=config.license_server, ) @@ -174,7 +174,7 @@ def start(self) -> None: } env = collections.ChainMap( - {"PORT_PYACP": str(port_acp), "PORT_FILETRANSFER": str(port_ft)}, self._env + {"PORT_ACP": str(port_acp), "PORT_FILETRANSFER": str(port_ft)}, self._env ) # The compose_file may be temporary, in particular if the package is a zipfile. diff --git a/src/ansys/acp/core/_server/launch.py b/src/ansys/acp/core/_server/launch.py index 741276d388..f78048829f 100644 --- a/src/ansys/acp/core/_server/launch.py +++ b/src/ansys/acp/core/_server/launch.py @@ -29,7 +29,7 @@ from ansys.tools.local_product_launcher.launch import launch_product from .acp_instance import ( - ACP, + ACPInstance, FiletransferStrategy, LocalFileTransferStrategy, RemoteFileTransferStrategy, @@ -45,7 +45,7 @@ def launch_acp( config: DirectLaunchConfig | DockerComposeLaunchConfig | None = None, launch_mode: LaunchMode | None = None, timeout: float | None = 30.0, -) -> ACP[ControllableServerProtocol]: +) -> ACPInstance[ControllableServerProtocol]: """Launch an ACP instance. Launch the ACP gRPC server with the given configuration. If no @@ -96,7 +96,7 @@ def launch_acp( else: raise ValueError("Invalid launch mode for ACP: " + str(launch_mode_evaluated)) - acp = ACP( + acp = ACPInstance( server=server_instance, filetransfer_strategy=filetransfer_strategy, channel=server_instance.channels[ServerKey.MAIN], diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index 480eb4323b..cf232eee2b 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -66,7 +66,7 @@ from ._object_cache import ObjectCacheMixin, constructor_with_cache if typing.TYPE_CHECKING: # pragma: no cover - from .._server import ACP + from .._server import ACPInstance @mark_grpc_properties @@ -196,7 +196,7 @@ class ServerWrapper: version: Version @classmethod - def from_acp_instance(cls, acp_instance: ACP[Any]) -> ServerWrapper: + def from_acp_instance(cls, acp_instance: ACPInstance[Any]) -> ServerWrapper: """Convert an ACP instance into the wrapper needed by tree objects.""" return cls( channel=acp_instance._channel, version=parse_version(acp_instance.server_version) diff --git a/src/ansys/acp/core/_workflow.py b/src/ansys/acp/core/_workflow.py index f108b42646..6e79bedea6 100644 --- a/src/ansys/acp/core/_workflow.py +++ b/src/ansys/acp/core/_workflow.py @@ -28,7 +28,7 @@ from typing import Any, Protocol from . import CADGeometry, UnitSystemType -from ._server.acp_instance import ACP +from ._server import ACPInstance from ._server.common import ServerProtocol from ._tree_objects import Model from ._typing_helper import PATH @@ -108,7 +108,7 @@ class _RemoteFileTransferStrategy: input files to the server. """ - def __init__(self, local_working_directory: _LocalWorkingDir, acp: ACP[ServerProtocol]): + def __init__(self, local_working_directory: _LocalWorkingDir, acp: ACPInstance[ServerProtocol]): self._local_working_directory = local_working_directory self._acp_instance = acp @@ -128,7 +128,7 @@ def upload_input_file_to_server(self, path: pathlib.Path) -> pathlib.PurePath: def _get_file_transfer_strategy( - acp: ACP[ServerProtocol], local_working_dir: _LocalWorkingDir + acp: ACPInstance[ServerProtocol], local_working_dir: _LocalWorkingDir ) -> _FileStrategy: if acp.is_remote: return _RemoteFileTransferStrategy( @@ -158,14 +158,14 @@ class ACPWorkflow: Format of the file to load. Options are ``"acp:h5"``, ``"ansys:cdb"``, ``"ansys:dat"``, and ``"ansys:h5"``. kwargs : - Additional keyword arguments to pass to the :meth:`.ACP.import_model` method. + Additional keyword arguments to pass to the :meth:`.ACPInstance.import_model` method. """ def __init__( self, *, - acp: ACP[ServerProtocol], + acp: ACPInstance[ServerProtocol], local_working_directory: PATH | None = None, local_file_path: PATH, file_format: str, @@ -186,7 +186,7 @@ def __init__( @classmethod def from_acph5_file( cls, - acp: ACP[ServerProtocol], + acp: ACPInstance[ServerProtocol], acph5_file_path: PATH, local_working_directory: PATH | None = None, ) -> "ACPWorkflow": @@ -213,7 +213,7 @@ def from_acph5_file( def from_cdb_or_dat_file( cls, *, - acp: ACP[ServerProtocol], + acp: ACPInstance[ServerProtocol], cdb_or_dat_file_path: PATH, unit_system: UnitSystemType = UnitSystemType.FROM_FILE, local_working_directory: PATH | None = None, diff --git a/src/ansys/acp/core/extras/__init__.py b/src/ansys/acp/core/extras/__init__.py new file mode 100644 index 0000000000..caecb4c948 --- /dev/null +++ b/src/ansys/acp/core/extras/__init__.py @@ -0,0 +1,29 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Extras of the Ansys Composites PrepPost module.""" + +from ansys.acp.core.extras.example_helpers import ExampleKeys, get_example_file # pragma: no cover + +__all__ = [ # pragma: no cover + "ExampleKeys", + "get_example_file", +] diff --git a/src/ansys/acp/core/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py similarity index 100% rename from src/ansys/acp/core/example_helpers.py rename to src/ansys/acp/core/extras/example_helpers.py diff --git a/tests/benchmarks/conftest.py b/tests/benchmarks/conftest.py index a1ec388173..f89d753ef3 100644 --- a/tests/benchmarks/conftest.py +++ b/tests/benchmarks/conftest.py @@ -95,7 +95,7 @@ def launcher_configuration(request): license_server = request.config.getoption(LICENSE_SERVER_OPTION_KEY) return pyacp.DockerComposeLaunchConfig( - image_name_pyacp=BENCHMARK_IMAGE_NAME, + image_name_acp=BENCHMARK_IMAGE_NAME, image_name_filetransfer=image_name_filetransfer, compose_file=SOURCE_ROOT_DIR / "docker-compose" / "docker-compose-benchmark.yaml", license_server=license_server, diff --git a/tests/conftest.py b/tests/conftest.py index 9e3c8b52c7..d249cdc8cf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,7 +35,7 @@ import pytest from ansys.acp.core import ( - ACP, + ACPInstance, ConnectLaunchConfig, DirectLaunchConfig, DockerComposeLaunchConfig, @@ -191,7 +191,7 @@ def _configure_launcher(request: pytest.FixtureRequest) -> None: product_name="ACP", launch_mode=LaunchMode.DOCKER_COMPOSE, config=DockerComposeLaunchConfig( - image_name_pyacp=image_name, + image_name_acp=image_name, image_name_filetransfer=image_name_filetransfer, license_server=license_server, keep_volume=False, @@ -209,14 +209,14 @@ def model_data_dir() -> pathlib.Path: @pytest.fixture(scope="session") -def acp_instance(_configure_launcher) -> Generator[ACP[ServerProtocol], None, None]: +def acp_instance(_configure_launcher) -> Generator[ACPInstance[ServerProtocol], None, None]: """Provide the currently active gRPC server.""" yield launch_acp(timeout=SERVER_STARTUP_TIMEOUT) @pytest.fixture(autouse=True) def check_grpc_server_before_run( - acp_instance: ACP[ServerProtocol], + acp_instance: ACPInstance[ServerProtocol], ) -> Generator[None, None, None]: """Check if the server still responds before running each test, otherwise restart it.""" try: From 0f3e00443085e62124da628b1b052bd22549da2c Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 20 Nov 2024 11:31:37 +0100 Subject: [PATCH 63/96] Document how to plot data on parts of the mesh (#678) Document that the 'mesh' parameter can be used when generating the PyVista objects to limit the scope of the created object. --- .../user_guide/howto/visualize_model.rst | 36 +++++++++++- src/ansys/acp/core/_plotter.py | 19 +++++-- src/ansys/acp/core/_typing_helper.py | 2 +- src/ansys/acp/core/extras/example_helpers.py | 2 +- .../common/linked_object_list_tester.py | 2 +- tests/unittests/test_plot_utils.py | 56 ++++++++++++++++++- 6 files changed, 106 insertions(+), 11 deletions(-) diff --git a/doc/source/user_guide/howto/visualize_model.rst b/doc/source/user_guide/howto/visualize_model.rst index 9c0c4b6017..fd16089726 100644 --- a/doc/source/user_guide/howto/visualize_model.rst +++ b/doc/source/user_guide/howto/visualize_model.rst @@ -21,7 +21,7 @@ Visualize model ... ExampleKeys.RACE_CAR_NOSE_ACPH5, pathlib.Path(tempdir.name) ... ) >>> path = acp.upload_file(input_file) - >>> model = acp.import_model(path=path) + >>> model = acp.import_model(path) >>> input_file_geometry = get_example_file( ... ExampleKeys.RACE_CAR_NOSE_STEP, pathlib.Path(tempdir.name) @@ -41,6 +41,14 @@ Visualize model >>> model.mesh.to_pyvista().plot() + You can also access and plot the mesh data for specific tree objects. For example, the following code plots the mesh for a modeling ply. + + .. pyvista-plot:: + :context: + + >>> modeling_ply = model.modeling_groups['nose'].modeling_plies['mp.nose.4'] + >>> modeling_ply.mesh.to_pyvista().plot() + .. _directions_plotter: @@ -54,7 +62,6 @@ Visualize model .. pyvista-plot:: :context: - >>> modeling_ply = model.modeling_groups['nose'].modeling_plies['mp.nose.4'] >>> elemental_data = modeling_ply.elemental_data >>> directions_plotter = pyacp.get_directions_plotter( ... model=model, @@ -69,6 +76,23 @@ Visualize model The color scheme used in this plot for the various components matches the ACP GUI. + The directions plot can be scoped to a specific region of the model by using the ``mesh`` parameter. For example, the following code only plots the part covered by the modeling ply. + + .. pyvista-plot:: + :context: + + >>> directions_plotter = pyacp.get_directions_plotter( + ... model=model, + ... mesh=modeling_ply.mesh, + ... components=[ + ... elemental_data.orientation, + ... elemental_data.fiber_direction + ... ], + ... length_factor=10., + ... culling_factor=10, + ... ) + >>> directions_plotter.show() + Showing the mesh data ~~~~~~~~~~~~~~~~~~~~~ @@ -89,6 +113,14 @@ Visualize model >>> pyvista_mesh = thickness_data.get_pyvista_mesh(mesh=model.mesh) >>> pyvista_mesh.plot() + Again, the ``mesh`` parameter can be used to limit the scope of the plot. + + .. pyvista-plot:: + :context: + + >>> pyvista_mesh = thickness_data.get_pyvista_mesh(mesh=model.element_sets["els_wing_assembly"].mesh) + >>> pyvista_mesh.plot() + Vector data ''''''''''' diff --git a/src/ansys/acp/core/_plotter.py b/src/ansys/acp/core/_plotter.py index 511ec68436..409d112fe6 100644 --- a/src/ansys/acp/core/_plotter.py +++ b/src/ansys/acp/core/_plotter.py @@ -25,8 +25,9 @@ import pyvista -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.acp.core import Model + from ansys.acp.core import MeshData from ansys.acp.core import VectorData from ansys.acp.core._utils.visualization import _replace_underscores_and_capitalize @@ -48,6 +49,7 @@ def get_directions_plotter( *, model: "Model", + mesh: "MeshData | None" = None, components: Sequence[Optional["VectorData"]], culling_factor: int = 1, length_factor: float = 1.0, @@ -57,8 +59,13 @@ def get_directions_plotter( Parameters ---------- - model: - ACP Model. + model : + ACP model. Determines the average element size used for scaling the + arrows. Unless explicitly specified, the model also determines the + mesh to plot. + mesh : + Mesh defining the scope of the plot. If not provided, the full mesh + of the model is used. components: List of components to plot. culling_factor : @@ -70,9 +77,11 @@ def get_directions_plotter( kwargs : Keyword arguments passed to the PyVista object constructor. """ + if mesh is None: + mesh = model.mesh plotter = pyvista.Plotter() - plotter.add_mesh(model.mesh.to_pyvista(), color="white", show_edges=True) + plotter.add_mesh(mesh.to_pyvista(), color="white", show_edges=True) for vector_data in components: if vector_data is None: @@ -80,7 +89,7 @@ def get_directions_plotter( color = _acp_direction_colors.get(vector_data.component_name, "black") plotter.add_mesh( vector_data.get_pyvista_glyphs( - mesh=model.mesh, + mesh=mesh, factor=model.average_element_size * length_factor, culling_factor=culling_factor, **kwargs, diff --git a/src/ansys/acp/core/_typing_helper.py b/src/ansys/acp/core/_typing_helper.py index 385be37b01..9334f5b316 100644 --- a/src/ansys/acp/core/_typing_helper.py +++ b/src/ansys/acp/core/_typing_helper.py @@ -36,7 +36,7 @@ # the enum member. # When type checking, always use the Python 3.10 workaround, otherwise # the StrEnum resolves as 'Any'. -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover class StrEnum(str, enum.Enum): """String enum.""" diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index 1ab881438c..caafe4acb3 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -35,7 +35,7 @@ from typing import TYPE_CHECKING -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.acp.core import ACPWorkflow _EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" diff --git a/tests/unittests/common/linked_object_list_tester.py b/tests/unittests/common/linked_object_list_tester.py index bde7624a7e..c62c215089 100644 --- a/tests/unittests/common/linked_object_list_tester.py +++ b/tests/unittests/common/linked_object_list_tester.py @@ -26,7 +26,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from mypy_extensions import DefaultNamedArg, KwArg import pytest diff --git a/tests/unittests/test_plot_utils.py b/tests/unittests/test_plot_utils.py index daebe6ad88..0420ab57f3 100644 --- a/tests/unittests/test_plot_utils.py +++ b/tests/unittests/test_plot_utils.py @@ -20,11 +20,39 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import pytest +from pytest_cases import parametrize_with_cases + from ansys.acp.core._plotter import get_directions_plotter from ansys.acp.core._utils.visualization import _replace_underscores_and_capitalize -def test_direction_plotter(acp_instance, load_model_from_tempfile): +@pytest.fixture +def model(load_model_from_tempfile): + with load_model_from_tempfile() as model: + yield model + + +def case_mesh_none_valid(): + return None + + +def case_model_mesh_valid(model): + return model.mesh + + +def case_other_mesh_valid(model, skip_before_version): + skip_before_version("25.1") + return model.element_sets["All_Elements"].mesh + + +def case_empty_mesh_invalid(model, skip_before_version): + skip_before_version("25.1") + return model.create_element_set().mesh + + +@parametrize_with_cases("mesh", cases=".", glob="*_valid") +def test_direction_plotter_valid_cases(model, mesh, load_model_from_tempfile): with load_model_from_tempfile() as model: modeling_ply = model.modeling_groups["ModelingGroup.1"].modeling_plies["ModelingPly.1"] analysis_ply = modeling_ply.production_plies["ProductionPly"].analysis_plies[ @@ -42,6 +70,7 @@ def test_direction_plotter(acp_instance, load_model_from_tempfile): ] plotter = get_directions_plotter( model=model, + mesh=mesh, components=components, ) @@ -49,3 +78,28 @@ def test_direction_plotter(acp_instance, load_model_from_tempfile): assert plotter.legend.GetEntryString(idx) == _replace_underscores_and_capitalize( data.component_name ) + + +@parametrize_with_cases("mesh", cases=".", glob="*_invalid") +def test_direction_plotter_invalid_cases(model, mesh, load_model_from_tempfile): + with load_model_from_tempfile() as model: + modeling_ply = model.modeling_groups["ModelingGroup.1"].modeling_plies["ModelingPly.1"] + analysis_ply = modeling_ply.production_plies["ProductionPly"].analysis_plies[ + "P1L1__ModelingPly.1" + ] + components = [ + analysis_ply.elemental_data.orientation, + analysis_ply.elemental_data.normal, + analysis_ply.elemental_data.reference_direction, + analysis_ply.elemental_data.fiber_direction, + analysis_ply.elemental_data.transverse_direction, + analysis_ply.elemental_data.draped_fiber_direction, + analysis_ply.elemental_data.draped_transverse_direction, + analysis_ply.elemental_data.material_1_direction, + ] + with pytest.raises(KeyError): + get_directions_plotter( + model=model, + mesh=mesh, + components=components, + ) From 2fb23c1d7b245d74b790a7c3d877284d8b4015f0 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 20 Nov 2024 15:45:44 +0100 Subject: [PATCH 64/96] Add PyMechanical helpers and examples (#673) Add helper functions for PyMechanical (remote instance) to: - export the HDF5 transfer file for ACP - import the solid model CDB file - import the plies from the composite definitions HDF5 file The HDF5 import and export uses internal-only API, meaning it works only on Windows. These helpers are used in two new examples for the PyMechanical workflow (shell and solid). The examples are run only when building the documentation on Windows, otherwise it is only rendered (without images). A third example exports the CDB file from PyMechanical, and uses that in a PyACP / PyMAPDL / PyDPF Composites workflow. Rename the previous `examples/pymechanical` directory to `examples/pymechanical_with_shim`, and update its README. Closes #444, closes #512, closes #676, closes #677. --- .../_static/gallery_thumbnails/README.md | 10 + ..._010_pymechanical_shell_workflow_thumb.png | Bin 0 -> 74497 bytes ..._011_pymechanical_solid_workflow_thumb.png | Bin 0 -> 60912 bytes ...012_pymechanical_to_cdb_workflow_thumb.png | Bin 0 -> 74522 bytes doc/source/api/index.rst | 1 + .../api/mechanical_integration_helpers.rst | 25 ++ doc/source/conf.py | 7 +- doc/source/index.rst | 21 +- examples/010_pymechanical_shell_workflow.py | 322 +++++++++++++++ examples/011_pymechanical_solid_workflow.py | 383 ++++++++++++++++++ examples/012_pymechanical_to_cdb_workflow.py | 319 +++++++++++++++ examples/pymechanical/Readme.md | 6 - examples/pymechanical_with_shim/Readme.md | 10 + .../acp_future/ACPFuture.csproj | 0 .../acp_future/ACPFuture.sln | 0 .../acp_future/Properties/AssemblyInfo.cs | 0 .../acp_future/Shims.cs | 0 .../acp_future/readme.md | 0 .../constants.py | 0 .../embedded_workflow.py | 0 .../generate_mesh.py | 0 .../geometry/flat_plate.agdb | Bin .../output/.gitkeep | 0 .../postprocess_results.py | 0 .../remote_workflow.py | 0 .../set_bc.py | 0 .../setup_acp_model.py | 0 poetry.lock | 252 +++++------- pyproject.toml | 2 + src/ansys/acp/core/__init__.py | 6 +- src/ansys/acp/core/extras/example_helpers.py | 2 + .../core/mechanical_integration_helpers.py | 173 ++++++++ 32 files changed, 1383 insertions(+), 156 deletions(-) create mode 100644 doc/source/_static/gallery_thumbnails/README.md create mode 100644 doc/source/_static/gallery_thumbnails/sphx_glr_010_pymechanical_shell_workflow_thumb.png create mode 100644 doc/source/_static/gallery_thumbnails/sphx_glr_011_pymechanical_solid_workflow_thumb.png create mode 100644 doc/source/_static/gallery_thumbnails/sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png create mode 100644 doc/source/api/mechanical_integration_helpers.rst create mode 100644 examples/010_pymechanical_shell_workflow.py create mode 100644 examples/011_pymechanical_solid_workflow.py create mode 100644 examples/012_pymechanical_to_cdb_workflow.py delete mode 100644 examples/pymechanical/Readme.md create mode 100644 examples/pymechanical_with_shim/Readme.md rename examples/{pymechanical => pymechanical_with_shim}/acp_future/ACPFuture.csproj (100%) rename examples/{pymechanical => pymechanical_with_shim}/acp_future/ACPFuture.sln (100%) rename examples/{pymechanical => pymechanical_with_shim}/acp_future/Properties/AssemblyInfo.cs (100%) rename examples/{pymechanical => pymechanical_with_shim}/acp_future/Shims.cs (100%) rename examples/{pymechanical => pymechanical_with_shim}/acp_future/readme.md (100%) rename examples/{pymechanical => pymechanical_with_shim}/constants.py (100%) rename examples/{pymechanical => pymechanical_with_shim}/embedded_workflow.py (100%) rename examples/{pymechanical => pymechanical_with_shim}/generate_mesh.py (100%) rename examples/{pymechanical => pymechanical_with_shim}/geometry/flat_plate.agdb (100%) rename examples/{pymechanical => pymechanical_with_shim}/output/.gitkeep (100%) rename examples/{pymechanical => pymechanical_with_shim}/postprocess_results.py (100%) rename examples/{pymechanical => pymechanical_with_shim}/remote_workflow.py (100%) rename examples/{pymechanical => pymechanical_with_shim}/set_bc.py (100%) rename examples/{pymechanical => pymechanical_with_shim}/setup_acp_model.py (100%) create mode 100644 src/ansys/acp/core/mechanical_integration_helpers.py diff --git a/doc/source/_static/gallery_thumbnails/README.md b/doc/source/_static/gallery_thumbnails/README.md new file mode 100644 index 0000000000..69a8b8e96c --- /dev/null +++ b/doc/source/_static/gallery_thumbnails/README.md @@ -0,0 +1,10 @@ +This directory contains thumbnails for the gallery examples which are +not built in CI. The thumbnails are used in the gallery index page. + +To update a thumbnail: +- remove the ``# sphinx_gallery_thumbnail_path`` configuration in the + example file +- build the documentation, running the example that you want to update +- copy the generated thumbnail from the example directory to this one +- re-add the ``# sphinx_gallery_thumbnail_path`` configuration in the + example file diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_010_pymechanical_shell_workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_010_pymechanical_shell_workflow_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff574b0c61c8872cc7aad9287a42a8a0e9160f5 GIT binary patch literal 74497 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 literal 0 HcmV?d00001 diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_011_pymechanical_solid_workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_011_pymechanical_solid_workflow_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..418dab84b6a8bf1d147d7c86756cdbc5e78e2bf8 GIT binary patch literal 60912 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 literal 0 HcmV?d00001 diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..9816156d7bad11f6dc34e0ee0b8e80b3f0573665 GIT binary patch literal 74522 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

>t-p;p5b9E%2j2v?dSC64n2)gp_+D;&0X_a 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%0D3w{_ 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 e`2SB1Jo)7_B83i(L09$x*Hudg3yj੖l5Tv94 literal 0 HcmV?d00001 diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 6d720e9b4c..df21e6b281 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -20,4 +20,5 @@ and attributes. other_utils workflow example_helpers + mechanical_integration_helpers internal diff --git a/doc/source/api/mechanical_integration_helpers.rst b/doc/source/api/mechanical_integration_helpers.rst new file mode 100644 index 0000000000..4732b11628 --- /dev/null +++ b/doc/source/api/mechanical_integration_helpers.rst @@ -0,0 +1,25 @@ +Mechanical integration helpers +------------------------------ + +.. warning:: + + The PyACP / PyMechanical integration is still experimental and will change + in future releases. + + It is also limited in the following ways: + + - Only remote Mechanical sessions on Windows are supported. + - Only one ACP shell or solid model can be imported into Mechanical. + + PyACP currently provides helper functions for integrating with PyMechanical. + These functions will be replaced with native PyMechanical functions in the future. + +.. currentmodule:: ansys.acp.core.mechanical_integration_helpers + +.. autosummary:: + :toctree: _autosummary + :template: autosummary/internal/base.rst.jinja2 + + export_mesh_for_acp + import_acp_composite_definitions + import_acp_solid_mesh diff --git a/doc/source/conf.py b/doc/source/conf.py index 19264b1540..7c666c1c37 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -3,6 +3,7 @@ from datetime import datetime import inspect import os +import sys import warnings import pyvista @@ -58,6 +59,7 @@ def _signature( from ansys.acp.core._typing_helper import StrEnum from ansys.dpf.composites.data_sources import ContinuousFiberCompositesFiles # noqa: F401 from ansys.dpf.core import UnitSystem # noqa: F401 + import ansys.mechanical.core as pymechanical # noqa: F401 signature = inspect.signature(subject, locals=locals(), eval_str=True) @@ -199,6 +201,7 @@ def _signature( "pyvista": ("https://docs.pyvista.org/version/stable", None), "ansys-dpf-core": ("https://dpf.docs.pyansys.com/version/stable/", None), "ansys-dpf-composites": ("https://composites.dpf.docs.pyansys.com/version/stable/", None), + "ansys-mechanical-core": ("https://mechanical.docs.pyansys.com/version/stable/", None), } nitpick_ignore = [ @@ -269,7 +272,9 @@ def _signature( # path where to save gallery generated examples "gallery_dirs": ["examples/gallery_examples"], # Pattern to search for example files - "filename_pattern": r"\.py", + "filename_pattern": ( + r".*\.py" if sys.platform == "win32" else r"^(?!.*pymechanical.*\.py).*\.py" + ), # execute PyMechanical examples only on Windows # Remove the "Download all examples" button from the top level gallery "download_all_examples": False, # Sort gallery example by filename instead of number of lines (default) diff --git a/doc/source/index.rst b/doc/source/index.rst index 144a38125c..8420d7f399 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -73,13 +73,24 @@ optimization of composite structures. Information on how to contribute to PyACP. +.. _limitations: + Limitations ^^^^^^^^^^^ * Field definitions are currently only supported through (Py)Mechanical. The workflow from PyACP to (Py)MADL ignores field definitions. -* The PyACP to PyMADL workflow does not fully support variable materials -* Visualization and mesh data of imported plies are not supported yet -* Section cuts cannot be visualized -* Sampling point analysis data is not available -* Imported solid model mapping statistics are not available +* The PyACP to PyMADL workflow does not fully support variable materials. +* The PyACP to PyMechanical workflow is experimental and has the following limitations: + + * It only works on Windows, with a remote (not embedded) PyMechanical session. + * Only one ACP shell or solid model is supported at a time. + * Named selections defined in ACP are not transferred to PyMechanical. + * The ``ansys.acp.core.mechanical_integration_helpers`` module will be + changed or removed in future versions, when the corresponding features + are available in PyMechanical directly. + +* Visualization and mesh data of imported plies are not supported yet. +* Section cuts cannot be visualized. +* Sampling point analysis data is not available. +* Imported solid model mapping statistics are not available. diff --git a/examples/010_pymechanical_shell_workflow.py b/examples/010_pymechanical_shell_workflow.py new file mode 100644 index 0000000000..79169cf3e1 --- /dev/null +++ b/examples/010_pymechanical_shell_workflow.py @@ -0,0 +1,322 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +""" + +.. _pymechanical_shell_example: + +PyMechanical shell workflow +=========================== + +This example shows how to set up a simple shell model with PyACP and +PyMechanical: + +- The geometry is imported into Mechanical and meshed. +- The mesh is exported to ACP. +- A simple lay-up is defined in ACP. +- Plies and materials are exported from ACP, and imported into Mechanical. +- Boundary conditions are set in Mechanical. +- The model is solved. +- The results are post-processed in PyDPF Composites. + +.. warning:: + + The PyACP / PyMechanical integration is still experimental. Refer to the + :ref:`limitations section ` for more information. + +""" + +# %% +# Import modules and start the Ansys products +# ------------------------------------------- + + +# %% +# Import the standard library and third-party dependencies. + +from concurrent.futures import ThreadPoolExecutor +import pathlib +import tempfile +import textwrap + +# %% +# Import PyACP, PyMechanical, and PyDPF Composites. + +# isort: off +import ansys.acp.core as pyacp +import ansys.dpf.composites as pydpf_composites +import ansys.mechanical.core as pymechanical + +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_010_pymechanical_shell_workflow_thumb.png' + +# %% +# Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` +# to start them in parallel. +with ThreadPoolExecutor() as executor: + futures = [ + executor.submit(pymechanical.launch_mechanical, batch=True), + executor.submit(pyacp.launch_acp), + executor.submit(pydpf_composites.server_helpers.connect_to_or_start_server), + ] + mechanical, acp, dpf = (fut.result() for fut in futures) + +# %% +# Get example input files +# ----------------------- +# +# Create a temporary working directory, and download the example input files +# to this directory. + +working_dir = tempfile.TemporaryDirectory() +working_dir_path = pathlib.Path(working_dir.name) +input_geometry = pyacp.extras.example_helpers.get_example_file( + pyacp.extras.example_helpers.ExampleKeys.CLASS40_AGDB, working_dir_path +) + +# %% +# Generate the mesh in PyMechanical +# --------------------------------- +# +# Load the geometry into Mechanical, generate the mesh, and export it to the +# appropriate transfer format for ACP. + +mesh_path = working_dir_path / "mesh.h5" +mechanical.run_python_script( + # This script runs in the Mechanical Python environment, which uses IronPython 2.7. + textwrap.dedent( + f"""\ + geometry_import = Model.GeometryImportGroup.AddGeometryImport() + + import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic + import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() + import_preferences.ProcessNamedSelections = True + import_preferences.ProcessCoordinateSystems = True + + geometry_file = {str(input_geometry)!r} + geometry_import.Import( + geometry_file, + import_format, + import_preferences + ) + + for body in Model.Geometry.GetChildren( + Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True + ): + body.Thickness = Quantity(1e-6, "m") + + Model.Mesh.GenerateMesh() + """ + ) +) +pyacp.mechanical_integration_helpers.export_mesh_for_acp(mechanical=mechanical, path=mesh_path) + +# %% +# Set up the ACP model +# -------------------- +# +# Setup basic ACP lay-up based on the mesh in ``mesh_path``, and export material and composite +# definition file to output_path. + +composite_definitions_h5 = "ACPCompositeDefinitions.h5" +matml_file = "materials.xml" # TODO: load an example materials XML file instead of defining the materials in ACP + + +mesh_path = acp.upload_file(mesh_path) + +model = acp.import_model(path=mesh_path, format="ansys:h5") + +mat = model.create_material(name="mat") + +mat.ply_type = "regular" +mat.engineering_constants.E1 = 1e12 +mat.engineering_constants.E2 = 1e11 +mat.engineering_constants.E3 = 1e11 +mat.engineering_constants.G12 = 1e10 +mat.engineering_constants.G23 = 1e10 +mat.engineering_constants.G31 = 1e10 +mat.engineering_constants.nu12 = 0.3 +mat.engineering_constants.nu13 = 0.3 +mat.engineering_constants.nu23 = 0.3 + +mat.strain_limits = pyacp.material_property_sets.ConstantStrainLimits.from_orthotropic_constants( + eXc=-0.01, + eYc=-0.01, + eZc=-0.01, + eXt=0.01, + eYt=0.01, + eZt=0.01, + eSxy=0.01, + eSyz=0.01, + eSxz=0.01, +) + +corecell_81kg_5mm = model.create_fabric(name="Corecell 81kg", thickness=0.005, material=mat) + +ros = model.create_rosette(name="ros", origin=(0, 0, 0)) + +oss = model.create_oriented_selection_set( + name="oss", + orientation_point=(-0, 0, 0), + orientation_direction=(0.0, 1, 0.0), + element_sets=[model.element_sets["All_Elements"]], + rosettes=[ros], +) + +mg = model.create_modeling_group(name="group") +mg.create_modeling_ply( + name="ply", + ply_material=corecell_81kg_5mm, + oriented_selection_sets=[oss], + ply_angle=45, + number_of_layers=1, + global_ply_nr=0, # add at the end +) +mg.create_modeling_ply( + name="ply2", + ply_material=corecell_81kg_5mm, + oriented_selection_sets=[oss], + ply_angle=0, + number_of_layers=2, + global_ply_nr=0, # add at the end +) + +# %% +# Update and Save the ACP model +# ----------------------------- + +model.update() + +if acp.is_remote: + export_path = pathlib.PurePosixPath(".") + +else: + export_path = working_dir_path # type: ignore + +model.export_shell_composite_definitions(export_path / composite_definitions_h5) +model.export_materials(export_path / matml_file) + +for filename in [ + composite_definitions_h5, + matml_file, +]: + acp.download_file(export_path / filename, working_dir_path / filename) + +# %% +# Import materials and plies into Mechanical +# ------------------------------------------ +# +# Import materials into Mechanical + +mechanical.run_python_script(f"Model.Materials.Import({str(working_dir_path / matml_file)!r})") + +# %% +# Import plies into Mechanical + +pyacp.mechanical_integration_helpers.import_acp_composite_definitions( + mechanical=mechanical, + path=working_dir_path / composite_definitions_h5, +) + +# %% +# Set boundary condition and solve +# --------------------------------- +# + +mechanical.run_python_script( + textwrap.dedent( + """\ + front_edge = Model.AddNamedSelection() + front_edge.Name = "Front Edge" + front_edge.ScopingMethod = GeometryDefineByType.Worksheet + + front_edge.GenerationCriteria.Add(None) + front_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + front_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + front_edge.GenerationCriteria[0].Operator = SelectionOperatorType.GreaterThan + front_edge.GenerationCriteria[0].Value = Quantity('-4.6 [m]') + front_edge.Generate() + + back_edge = Model.AddNamedSelection() + back_edge.Name = "Back Edge" + back_edge.ScopingMethod = GeometryDefineByType.Worksheet + + back_edge.GenerationCriteria.Add(None) + back_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + back_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + back_edge.GenerationCriteria[0].Operator = SelectionOperatorType.LessThan + back_edge.GenerationCriteria[0].Value = Quantity('-7.8 [m]') + back_edge.Generate() + + analysis = Model.AddStaticStructuralAnalysis() + + fixed_support = analysis.AddFixedSupport() + fixed_support.Location = back_edge + + force = analysis.AddForce() + force.DefineBy = LoadDefineBy.Components + force.XComponent.Output.SetDiscreteValue(0, Quantity(1e6, "N")) + force.Location = front_edge + + analysis.Solution.Solve(True) + """ + ) +) + +rst_file = [filename for filename in mechanical.list_files() if filename.endswith(".rst")][0] +matml_out = [filename for filename in mechanical.list_files() if filename.endswith("MatML.xml")][0] + +# %% +# Postprocess results +# ------------------- +# +# Evaluate the failure criteria using the PyDPF Composites. + + +max_strain = pydpf_composites.failure_criteria.MaxStrainCriterion() +cfc = pydpf_composites.failure_criteria.CombinedFailureCriterion( + name="Combined Failure Criterion", + failure_criteria=[max_strain], +) + +composite_model = pydpf_composites.composite_model.CompositeModel( + composite_files=pydpf_composites.data_sources.ContinuousFiberCompositesFiles( + rst=rst_file, + composite={ + "shell": pydpf_composites.data_sources.CompositeDefinitionFiles( + definition=working_dir_path / composite_definitions_h5 + ), + }, + engineering_data=working_dir_path / matml_file, + ), + server=dpf, +) + +# Evaluate the failure criteria +output_all_elements = composite_model.evaluate_failure_criteria(cfc) + +# Query and plot the results +irf_field = output_all_elements.get_field( + {"failure_label": pydpf_composites.constants.FailureOutput.FAILURE_VALUE} +) + +irf_field.plot() diff --git a/examples/011_pymechanical_solid_workflow.py b/examples/011_pymechanical_solid_workflow.py new file mode 100644 index 0000000000..1abca53159 --- /dev/null +++ b/examples/011_pymechanical_solid_workflow.py @@ -0,0 +1,383 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +""" + +.. _pymechanical_solid_example: + +PyMechanical solid workflow +=========================== + +This example shows how to set up a simple solid model with PyACP and +PyMechanical: + +- The geometry is imported into Mechanical and meshed. +- The mesh is exported to ACP. +- A simple lay-up and solid model is defined in ACP. +- The solid model is exported, to a CDB file and a composite definition file. +- In a separate Mechanical instance, the solid model is imported. +- Materials and plies are imported. +- Boundary conditions are set. +- The model is solved. +- The results are post-processed in PyDPF Composites. + +.. warning:: + + The PyACP / PyMechanical integration is still experimental. Refer to the + :ref:`limitations section ` for more information. + +""" + +# %% +# Import modules and start the Ansys products +# ------------------------------------------- + + +# %% +# Import the standard library and third-party dependencies. + +from concurrent.futures import ThreadPoolExecutor +import pathlib +import tempfile +import textwrap + +# %% +# Import PyACP, PyMechanical, and PyDPF Composites. + +# isort: off + +import ansys.acp.core as pyacp +import ansys.dpf.composites as pydpf_composites +import ansys.mechanical.core as pymechanical + +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_011_pymechanical_solid_workflow_thumb.png' + +# %% +# Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` +# to start them in parallel. +with ThreadPoolExecutor() as executor: + futures = [ + executor.submit(pymechanical.launch_mechanical, batch=True), + executor.submit(pymechanical.launch_mechanical, batch=True), + executor.submit(pyacp.launch_acp), + executor.submit(pydpf_composites.server_helpers.connect_to_or_start_server), + ] + mechanical_shell_geometry, mechanical_solid_model, acp, dpf = (fut.result() for fut in futures) + +# %% +# Get example input files +# ----------------------- +# +# Create a temporary working directory, and download the example input files +# to this directory. + +working_dir = tempfile.TemporaryDirectory() +working_dir_path = pathlib.Path(working_dir.name) +input_geometry = pyacp.extras.example_helpers.get_example_file( + pyacp.extras.example_helpers.ExampleKeys.CLASS40_AGDB, working_dir_path +) + + +# %% +# Generate the mesh in PyMechanical +# --------------------------------- +# +# Load the geometry into Mechanical, generate the mesh, and export it to the +# appropriate transfer format for ACP. + +mesh_path = working_dir_path / "mesh.h5" +mechanical_shell_geometry.run_python_script( + # This script runs in the Mechanical Python environment, which uses IronPython 2.7. + textwrap.dedent( + f"""\ + geometry_import = Model.GeometryImportGroup.AddGeometryImport() + + import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic + import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() + import_preferences.ProcessNamedSelections = True + import_preferences.ProcessCoordinateSystems = True + + geometry_file = {str(input_geometry)!r} + geometry_import.Import( + geometry_file, + import_format, + import_preferences + ) + + for body in Model.Geometry.GetChildren( + Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True + ): + body.Thickness = Quantity(1e-6, "m") + + hull = Model.AddNamedSelection() + hull.Name = "hull" + hull.ScopingMethod = GeometryDefineByType.Worksheet + # Add all faces with Z location < 0.9 m + hull.GenerationCriteria.Add(None) + hull.GenerationCriteria[0].EntityType = SelectionType.GeoFace + hull.GenerationCriteria[0].Operator = SelectionOperatorType.LessThan + hull.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationZ + hull.GenerationCriteria[0].Value = Quantity('0.9 [m]') + # Remove keeltower + hull.GenerationCriteria.Add(None) + hull.GenerationCriteria[1].Action = SelectionActionType.Remove + hull.GenerationCriteria[1].Criterion = SelectionCriterionType.LocationX + hull.GenerationCriteria[1].Operator = SelectionOperatorType.RangeInclude + hull.GenerationCriteria[1].LowerBound = Quantity('-6.7 [m]') + hull.GenerationCriteria[1].UpperBound = Quantity('-5.9 [m]') + # Add back keeltower bottom + hull.GenerationCriteria.Add(None) + hull.GenerationCriteria[2].Criterion = SelectionCriterionType.LocationZ + hull.GenerationCriteria[2].Operator = SelectionOperatorType.LessThan + hull.GenerationCriteria[2].Value = Quantity('-0.25 [m]') + # Remove bulkhead + hull.GenerationCriteria.Add(None) + hull.GenerationCriteria[3].Action = SelectionActionType.Remove + hull.GenerationCriteria[3].Criterion = SelectionCriterionType.LocationX + hull.GenerationCriteria[3].Operator = SelectionOperatorType.RangeInclude + hull.GenerationCriteria[3].LowerBound = Quantity('-5.7 [m]') + hull.GenerationCriteria[3].UpperBound = Quantity('-5.6 [m]') + hull.Generate() + + Model.Mesh.GenerateMesh() + """ + ) +) +pyacp.mechanical_integration_helpers.export_mesh_for_acp( + mechanical=mechanical_shell_geometry, path=mesh_path +) + + +# %% +# Set up the ACP model +# -------------------- +# +# Setup basic ACP lay-up based on the mesh in ``mesh_path``, and export the following +# files to ``output_path``: +# +# - Materials XML file +# - Composite definitions HDF5 file +# - Solid model composite definitions HDF5 file +# - Solid model CDB file + +matml_file = "materials.xml" # TODO: load an example materials XML file instead of defining the materials in ACP +solid_model_cdb_file = "SolidModel.cdb" +solid_model_composite_definitions_h5 = "SolidModel.h5" + + +mesh_path = acp.upload_file(mesh_path) + +model = acp.import_model(path=mesh_path, format="ansys:h5") + +mat = model.create_material(name="mat") + +mat.ply_type = "regular" +mat.engineering_constants.E1 = 1e12 +mat.engineering_constants.E2 = 1e11 +mat.engineering_constants.E3 = 1e11 +mat.engineering_constants.G12 = 1e10 +mat.engineering_constants.G23 = 1e10 +mat.engineering_constants.G31 = 1e10 +mat.engineering_constants.nu12 = 0.3 +mat.engineering_constants.nu13 = 0.3 +mat.engineering_constants.nu23 = 0.3 + +mat.strain_limits = pyacp.material_property_sets.ConstantStrainLimits.from_orthotropic_constants( + eXc=-0.01, + eYc=-0.01, + eZc=-0.01, + eXt=0.01, + eYt=0.01, + eZt=0.01, + eSxy=0.01, + eSyz=0.01, + eSxz=0.01, +) + +corecell_81kg_5mm = model.create_fabric(name="Corecell 81kg", thickness=0.005, material=mat) + +ros = model.create_rosette(name="ros", origin=(0, 0, 0)) + +oss = model.create_oriented_selection_set( + name="oss", + orientation_point=(-0, 0, 0), + orientation_direction=(0.0, 1, 0.0), + element_sets=[model.element_sets["All_Elements"]], + rosettes=[ros], +) + +mg = model.create_modeling_group(name="group") +mg.create_modeling_ply( + name="ply", + ply_material=corecell_81kg_5mm, + oriented_selection_sets=[oss], + ply_angle=45, + number_of_layers=1, + global_ply_nr=0, # add at the end +) +mg.create_modeling_ply( + name="ply2", + ply_material=corecell_81kg_5mm, + oriented_selection_sets=[oss], + ply_angle=0, + number_of_layers=2, + global_ply_nr=0, # add at the end +) + +solid_model = model.create_solid_model( + element_sets=[model.element_sets["hull"]], +) + +# %% +# Update and Save the ACP model +# ----------------------------- + +model.update() + +if acp.is_remote: + export_path = pathlib.PurePosixPath(".") + +else: + export_path = working_dir_path # type: ignore + +model.export_materials(export_path / matml_file) +solid_model.export(export_path / solid_model_cdb_file, format="ansys:cdb") +solid_model.export(export_path / solid_model_composite_definitions_h5, format="ansys:h5") + +for filename in [ + matml_file, + solid_model_cdb_file, + solid_model_composite_definitions_h5, +]: + acp.download_file(export_path / filename, working_dir_path / filename) + + +# %% +# Import mesh, materials and plies into Mechanical +# ------------------------------------------------ +# +# Import geometry, mesh, and named selections into Mechanical + +pyacp.mechanical_integration_helpers.import_acp_solid_mesh( + mechanical=mechanical_solid_model, cdb_path=working_dir_path / solid_model_cdb_file +) + + +# %% +# Import materials into Mechanical + +mechanical_solid_model.run_python_script( + f"Model.Materials.Import({str(working_dir_path / matml_file)!r})" +) + +# %% +# Import plies into Mechanical + +pyacp.mechanical_integration_helpers.import_acp_composite_definitions( + mechanical=mechanical_solid_model, path=working_dir_path / solid_model_composite_definitions_h5 +) + + +# %% +# Set boundary condition and solve +# --------------------------------- +# +# Set boundary condition and solve + +mechanical_solid_model.run_python_script( + textwrap.dedent( + """\ + analysis = Model.AddStaticStructuralAnalysis() + + front_face = Model.AddNamedSelection() + front_face.Name = "front_face" + front_face.ScopingMethod = GeometryDefineByType.Worksheet + front_face.GenerationCriteria.Add(None) + front_face.GenerationCriteria[0].EntityType = SelectionType.GeoFace + front_face.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + front_face.GenerationCriteria[0].Operator = SelectionOperatorType.Largest + front_face.Generate() + + back_face = Model.AddNamedSelection() + back_face.Name = "back_face" + back_face.ScopingMethod = GeometryDefineByType.Worksheet + back_face.GenerationCriteria.Add(None) + back_face.GenerationCriteria[0].EntityType = SelectionType.GeoFace + back_face.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + back_face.GenerationCriteria[0].Operator = SelectionOperatorType.Smallest + back_face.Generate() + + fixed_support = analysis.AddFixedSupport() + fixed_support.Location = back_face + + force = analysis.AddForce() + force.DefineBy = LoadDefineBy.Components + force.XComponent.Output.SetDiscreteValue(0, Quantity(1e5, "N")) + force.Location = front_face + + analysis.Solve(True) + """ + ) +) + +rst_file = [ + filename for filename in mechanical_solid_model.list_files() if filename.endswith(".rst") +][0] +matml_out = [ + filename for filename in mechanical_solid_model.list_files() if filename.endswith("MatML.xml") +][0] + +# %% +# Postprocess results +# ------------------- +# +# Evaluate the failure criteria using the PyDPF Composites. + +max_strain = pydpf_composites.failure_criteria.MaxStrainCriterion() +cfc = pydpf_composites.failure_criteria.CombinedFailureCriterion( + name="Combined Failure Criterion", + failure_criteria=[max_strain], +) + +composite_model = pydpf_composites.composite_model.CompositeModel( + composite_files=pydpf_composites.data_sources.ContinuousFiberCompositesFiles( + rst=rst_file, + composite={ + "solid": pydpf_composites.data_sources.CompositeDefinitionFiles( + definition=working_dir_path / solid_model_composite_definitions_h5 + ), + }, + engineering_data=working_dir_path / matml_file, + ), + server=dpf, +) + +# Evaluate the failure criteria +output_all_elements = composite_model.evaluate_failure_criteria(cfc) + +# Query and plot the results +irf_field = output_all_elements.get_field( + {"failure_label": pydpf_composites.constants.FailureOutput.FAILURE_VALUE} +) + +irf_field.plot() diff --git a/examples/012_pymechanical_to_cdb_workflow.py b/examples/012_pymechanical_to_cdb_workflow.py new file mode 100644 index 0000000000..29af5e8265 --- /dev/null +++ b/examples/012_pymechanical_to_cdb_workflow.py @@ -0,0 +1,319 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +""" + +.. _pymechanical_to_cdb_example: + +PyMechanical to CDB shell workflow +================================== + +This example shows how to set up a workflow that uses PyMechanical to mesh the +geometry and define the load case, PyACP to define a layup, PyMAPDL to solve the +model, and PyDPF Composites to post-process the results. + +This workflow does *not* suffer from the limitations of the PyACP to +PyMechanical integration. + +""" + + +# %% +# Import modules and start the Ansys products +# ------------------------------------------- + + +# %% +# Import the standard library and third-party dependencies. + +from concurrent.futures import ThreadPoolExecutor +import pathlib +import tempfile +import textwrap + +# %% +# Import PyACP, PyMechanical, and PyDPF Composites. + +# isort: off +import ansys.acp.core as pyacp +import ansys.dpf.core as pydpf_core +import ansys.dpf.composites as pydpf_composites +import ansys.mapdl.core as pymapdl +import ansys.mechanical.core as pymechanical + +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png' +# sphinx_gallery_thumbnail_number = -1 + +# %% +# Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` +# to start them in parallel. +with ThreadPoolExecutor() as executor: + futures = [ + executor.submit(pymechanical.launch_mechanical, batch=True), + executor.submit(pyacp.launch_acp), + executor.submit(pymapdl.launch_mapdl), + executor.submit(pydpf_composites.server_helpers.connect_to_or_start_server), + ] + mechanical, acp, mapdl, dpf = (fut.result() for fut in futures) + mapdl.clear() + +# %% +# Get example input files +# ----------------------- +# +# Create a temporary working directory, and download the example input files +# to this directory. + +working_dir = tempfile.TemporaryDirectory() +working_dir_path = pathlib.Path(working_dir.name) +input_geometry = pyacp.extras.example_helpers.get_example_file( + pyacp.extras.example_helpers.ExampleKeys.CLASS40_AGDB, working_dir_path +) + +# %% +# Generate the mesh in PyMechanical +# --------------------------------- +# +# Load the geometry into Mechanical, generate the mesh, and define the +# load case. + +cdb_path_initial = working_dir_path / "model_from_mechanical.cdb" +mechanical.run_python_script( + # This script runs in the Mechanical Python environment, which uses IronPython 2.7. + textwrap.dedent( + f"""\ + # Import the geometry + geometry_import = Model.GeometryImportGroup.AddGeometryImport() + + import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic + import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() + import_preferences.ProcessNamedSelections = True + import_preferences.ProcessCoordinateSystems = True + + geometry_file = {str(input_geometry)!r} + geometry_import.Import( + geometry_file, + import_format, + import_preferences + ) + + # The thickness will be overridden by the ACP model, but is required + # for the model to be valid. + for body in Model.Geometry.GetChildren( + Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True + ): + body.Thickness = Quantity(1e-6, "m") + + # Define named selections at the front and back edges + front_edge = Model.AddNamedSelection() + front_edge.Name = "Front Edge" + front_edge.ScopingMethod = GeometryDefineByType.Worksheet + + front_edge.GenerationCriteria.Add(None) + front_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + front_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + front_edge.GenerationCriteria[0].Operator = SelectionOperatorType.GreaterThan + front_edge.GenerationCriteria[0].Value = Quantity('-4.6 [m]') + front_edge.Generate() + + back_edge = Model.AddNamedSelection() + back_edge.Name = "Back Edge" + back_edge.ScopingMethod = GeometryDefineByType.Worksheet + + back_edge.GenerationCriteria.Add(None) + back_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + back_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + back_edge.GenerationCriteria[0].Operator = SelectionOperatorType.LessThan + back_edge.GenerationCriteria[0].Value = Quantity('-7.8 [m]') + back_edge.Generate() + + # Create a static structural analysis, and define the boundary + # conditions (fixed support at the back edge, force at the front edge). + analysis = Model.AddStaticStructuralAnalysis() + + fixed_support = analysis.AddFixedSupport() + fixed_support.Location = back_edge + + force = analysis.AddForce() + force.DefineBy = LoadDefineBy.Components + force.XComponent.Output.SetDiscreteValue(0, Quantity(1e6, "N")) + force.Location = front_edge + + # Export the model to a CDB file + analysis.WriteInputFile({str(cdb_path_initial)!r}) + """ + ) +) + +# %% +# Set up the ACP model +# -------------------- +# +# Setup basic ACP lay-up based on the CDB file. + + +model = acp.import_model(path=acp.upload_file(cdb_path_initial), format="ansys:cdb") + +mat = model.create_material(name="mat") + +mat.ply_type = "regular" +mat.engineering_constants.E1 = 1e12 +mat.engineering_constants.E2 = 1e11 +mat.engineering_constants.E3 = 1e11 +mat.engineering_constants.G12 = 1e10 +mat.engineering_constants.G23 = 1e10 +mat.engineering_constants.G31 = 1e10 +mat.engineering_constants.nu12 = 0.3 +mat.engineering_constants.nu13 = 0.3 +mat.engineering_constants.nu23 = 0.3 + +mat.strain_limits = pyacp.material_property_sets.ConstantStrainLimits.from_orthotropic_constants( + eXc=-0.01, + eYc=-0.01, + eZc=-0.01, + eXt=0.01, + eYt=0.01, + eZt=0.01, + eSxy=0.01, + eSyz=0.01, + eSxz=0.01, +) + +corecell_81kg_5mm = model.create_fabric(name="Corecell 81kg", thickness=0.005, material=mat) + +ros = model.create_rosette(name="ros", origin=(0, 0, 0)) + +oss = model.create_oriented_selection_set( + name="oss", + orientation_point=(-0, 0, 0), + orientation_direction=(0.0, 1, 0.0), + element_sets=[model.element_sets["All_Elements"]], + rosettes=[ros], +) + +mg = model.create_modeling_group(name="group") +mg.create_modeling_ply( + name="ply", + ply_material=corecell_81kg_5mm, + oriented_selection_sets=[oss], + ply_angle=45, + number_of_layers=1, + global_ply_nr=0, # add at the end +) +mg.create_modeling_ply( + name="ply2", + ply_material=corecell_81kg_5mm, + oriented_selection_sets=[oss], + ply_angle=0, + number_of_layers=2, + global_ply_nr=0, # add at the end +) + +# %% +# Update and Save the ACP model +# ----------------------------- + +model.update() + +if acp.is_remote: + export_path = pathlib.PurePosixPath(".") + +else: + export_path = working_dir_path # type: ignore + +cdb_filename_out = "model_from_acp.cdb" +composite_definitions_h5_filename = "ACPCompositeDefinitions.h5" +matml_filename = "materials.xml" + +model.export_analysis_model(export_path / cdb_filename_out) +model.export_shell_composite_definitions(export_path / composite_definitions_h5_filename) +model.export_materials(export_path / matml_filename) + +for filename in [cdb_filename_out, composite_definitions_h5_filename, matml_filename]: + acp.download_file(export_path / filename, working_dir_path / filename) + +# %% +# Solve with PyMAPDL +# ------------------ + +mapdl.clear() +# %% +# Load the CDB file into PyMAPDL. +mapdl.input(str(working_dir_path / cdb_filename_out)) + +# %% +# Solve the model. +mapdl.allsel() +mapdl.slashsolu() +mapdl.solve() + +# %% +# Show the displacements in postprocessing. +mapdl.post1() +mapdl.set("last") +mapdl.post_processing.plot_nodal_displacement(component="NORM") + +# %% +# Download the RST file for further postprocessing. +rstfile_name = f"{mapdl.jobname}.rst" +rst_file_local_path = working_dir_path / rstfile_name +mapdl.download(rstfile_name, working_dir_path) + +# %% +# Postprocessing with PyDPF Composites +# ------------------------------------ +# +# Specify the combined failure criterion. +max_strain = pydpf_composites.failure_criteria.MaxStrainCriterion() + +cfc = pydpf_composites.failure_criteria.CombinedFailureCriterion( + name="Combined Failure Criterion", + failure_criteria=[max_strain], +) + +# %% +# Create the composite model and configure its input. +composite_model = pydpf_composites.composite_model.CompositeModel( + composite_files=pydpf_composites.data_sources.ContinuousFiberCompositesFiles( + rst=rst_file_local_path, + composite={ + "shell": pydpf_composites.data_sources.CompositeDefinitionFiles( + definition=working_dir_path / composite_definitions_h5_filename + ), + }, + engineering_data=working_dir_path / matml_filename, + ), + default_unit_system=pydpf_core.unit_system.unit_systems.solver_nmm, + server=dpf, +) + +# %% +# Evaluate the failure criteria. +output_all_elements = composite_model.evaluate_failure_criteria(cfc) + +# %% +# Query and plot the results. +irf_field = output_all_elements.get_field( + {"failure_label": pydpf_composites.constants.FailureOutput.FAILURE_VALUE} +) +irf_field.plot() diff --git a/examples/pymechanical/Readme.md b/examples/pymechanical/Readme.md deleted file mode 100644 index ca3df560ba..0000000000 --- a/examples/pymechanical/Readme.md +++ /dev/null @@ -1,6 +0,0 @@ -This is an example of a full composite workflow using PyMechanical (without Workbench). It is currently not part of the PyACP example suite. - -The example works with both "remote" (remote_workflow.py) and "embedded" (embedded_workflow) PyMechanical. -For the example's documentation, see "embedded_workflow.py". - -This example works on Windows and could not be tested on Linux due to https://github.com/ansys/pymechanical/issues/352. \ No newline at end of file diff --git a/examples/pymechanical_with_shim/Readme.md b/examples/pymechanical_with_shim/Readme.md new file mode 100644 index 0000000000..688c4b2a24 --- /dev/null +++ b/examples/pymechanical_with_shim/Readme.md @@ -0,0 +1,10 @@ +This is an example of a shell workflow using PyMechanical, using the 'shim' defined +in the ``acp_future`` sub-directory. The shim enables running the workflow on both +remote and embedded PyMechanical. + +Variants of this example _not_ using the shim have been integrated into the main +examples directory. However, these work only on Windows, and only with the remote +PyMechanical. + +This example works on Windows, but could not be tested on Linux due to +https://github.com/ansys/pymechanical/issues/352. diff --git a/examples/pymechanical/acp_future/ACPFuture.csproj b/examples/pymechanical_with_shim/acp_future/ACPFuture.csproj similarity index 100% rename from examples/pymechanical/acp_future/ACPFuture.csproj rename to examples/pymechanical_with_shim/acp_future/ACPFuture.csproj diff --git a/examples/pymechanical/acp_future/ACPFuture.sln b/examples/pymechanical_with_shim/acp_future/ACPFuture.sln similarity index 100% rename from examples/pymechanical/acp_future/ACPFuture.sln rename to examples/pymechanical_with_shim/acp_future/ACPFuture.sln diff --git a/examples/pymechanical/acp_future/Properties/AssemblyInfo.cs b/examples/pymechanical_with_shim/acp_future/Properties/AssemblyInfo.cs similarity index 100% rename from examples/pymechanical/acp_future/Properties/AssemblyInfo.cs rename to examples/pymechanical_with_shim/acp_future/Properties/AssemblyInfo.cs diff --git a/examples/pymechanical/acp_future/Shims.cs b/examples/pymechanical_with_shim/acp_future/Shims.cs similarity index 100% rename from examples/pymechanical/acp_future/Shims.cs rename to examples/pymechanical_with_shim/acp_future/Shims.cs diff --git a/examples/pymechanical/acp_future/readme.md b/examples/pymechanical_with_shim/acp_future/readme.md similarity index 100% rename from examples/pymechanical/acp_future/readme.md rename to examples/pymechanical_with_shim/acp_future/readme.md diff --git a/examples/pymechanical/constants.py b/examples/pymechanical_with_shim/constants.py similarity index 100% rename from examples/pymechanical/constants.py rename to examples/pymechanical_with_shim/constants.py diff --git a/examples/pymechanical/embedded_workflow.py b/examples/pymechanical_with_shim/embedded_workflow.py similarity index 100% rename from examples/pymechanical/embedded_workflow.py rename to examples/pymechanical_with_shim/embedded_workflow.py diff --git a/examples/pymechanical/generate_mesh.py b/examples/pymechanical_with_shim/generate_mesh.py similarity index 100% rename from examples/pymechanical/generate_mesh.py rename to examples/pymechanical_with_shim/generate_mesh.py diff --git a/examples/pymechanical/geometry/flat_plate.agdb b/examples/pymechanical_with_shim/geometry/flat_plate.agdb similarity index 100% rename from examples/pymechanical/geometry/flat_plate.agdb rename to examples/pymechanical_with_shim/geometry/flat_plate.agdb diff --git a/examples/pymechanical/output/.gitkeep b/examples/pymechanical_with_shim/output/.gitkeep similarity index 100% rename from examples/pymechanical/output/.gitkeep rename to examples/pymechanical_with_shim/output/.gitkeep diff --git a/examples/pymechanical/postprocess_results.py b/examples/pymechanical_with_shim/postprocess_results.py similarity index 100% rename from examples/pymechanical/postprocess_results.py rename to examples/pymechanical_with_shim/postprocess_results.py diff --git a/examples/pymechanical/remote_workflow.py b/examples/pymechanical_with_shim/remote_workflow.py similarity index 100% rename from examples/pymechanical/remote_workflow.py rename to examples/pymechanical_with_shim/remote_workflow.py diff --git a/examples/pymechanical/set_bc.py b/examples/pymechanical_with_shim/set_bc.py similarity index 100% rename from examples/pymechanical/set_bc.py rename to examples/pymechanical_with_shim/set_bc.py diff --git a/examples/pymechanical/setup_acp_model.py b/examples/pymechanical_with_shim/setup_acp_model.py similarity index 100% rename from examples/pymechanical/setup_acp_model.py rename to examples/pymechanical_with_shim/setup_acp_model.py diff --git a/poetry.lock b/poetry.lock index 23e1f83975..9f72df6eef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -564,17 +564,16 @@ tests = ["pyfakefs (==5.7.1)", "pytest (==8.3.3)", "pytest-cov (==6.0.0)"] [[package]] name = "ansys-tools-visualization-interface" -version = "0.4.7" +version = "0.5.0" description = "A Python visualization interface for PyAnsys libraries" optional = false python-versions = "<4,>=3.10" files = [ - {file = "ansys_tools_visualization_interface-0.4.7-py3-none-any.whl", hash = "sha256:c1555822395e3c75f39234f38a9ed999aba9e4bf047a02c820a1c7789cd926f6"}, - {file = "ansys_tools_visualization_interface-0.4.7.tar.gz", hash = "sha256:d173b71c46089ce4c21ccb2b1f690ae23712ce200b1d613d54a01f364cae9e7c"}, + {file = "ansys_tools_visualization_interface-0.5.0-py3-none-any.whl", hash = "sha256:0f73d515e039b4335ab8b902e65fa6d673db809ba07957164adccc08d8906565"}, + {file = "ansys_tools_visualization_interface-0.5.0.tar.gz", hash = "sha256:a8410f9761f2e9be13961516f414acfd16d6b345015252afc129fc4cc43c38a1"}, ] [package.dependencies] -beartype = ">=0.17.0,<1" pyvista = ">=0.43.0,<1" trame = ">=3.6.0,<4" trame-vtk = ">=2.8.7,<3" @@ -582,7 +581,7 @@ trame-vuetify = ">=2.4.3,<3" websockets = ">=12.0,<14" [package.extras] -doc = ["ansys-fluent-core (==0.26.1)", "ansys-sphinx-theme (==1.1.6)", "jupyter_sphinx (==0.5.3)", "jupytext (==1.16.4)", "nbsphinx (==0.9.5)", "numpydoc (==1.8.0)", "sphinx (==8.1.3)", "sphinx-autoapi (==3.3.2)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-jinja (==2.0.2)", "sphinx_design (==0.6.1)"] +doc = ["ansys-fluent-core (==0.26.1)", "ansys-sphinx-theme (==1.1.7)", "jupyter_sphinx (==0.5.3)", "jupytext (==1.16.4)", "nbsphinx (==0.9.5)", "numpydoc (==1.8.0)", "sphinx (==8.1.3)", "sphinx-autoapi (==3.3.3)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-jinja (==2.0.2)", "sphinx_design (==0.6.1)"] tests = ["pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-pyvista (==0.1.9)"] [[package]] @@ -782,24 +781,6 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] -[[package]] -name = "beartype" -version = "0.19.0" -description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "beartype-0.19.0-py3-none-any.whl", hash = "sha256:33b2694eda0daf052eb2aff623ed9a8a586703bbf0a90bbc475a83bbf427f699"}, - {file = "beartype-0.19.0.tar.gz", hash = "sha256:de42dfc1ba5c3710fde6c3002e3bd2cad236ed4d2aabe876345ab0b4234a6573"}, -] - -[package.extras] -dev = ["autoapi (>=0.9.0)", "coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] -doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] -test = ["coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] -test-tox = ["equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "typing-extensions (>=3.10.0.0)"] -test-tox-coverage = ["coverage (>=5.5)"] - [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -1735,13 +1716,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.149.0" +version = "2.151.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.149.0-py2.py3-none-any.whl", hash = "sha256:1a5232e9cfed8c201799d9327e4d44dc7ea7daa3c6e1627fca41aa201539c0da"}, - {file = "google_api_python_client-2.149.0.tar.gz", hash = "sha256:b9d68c6b14ec72580d66001bd33c5816b78e2134b93ccc5cf8f624516b561750"}, + {file = "google_api_python_client-2.151.0-py2.py3-none-any.whl", hash = "sha256:4427b2f47cd88b0355d540c2c52215f68c337f3bc9d6aae1ceeae4525977504c"}, + {file = "google_api_python_client-2.151.0.tar.gz", hash = "sha256:a9d26d630810ed4631aea21d1de3e42072f98240aaf184a8a1a874a371115034"}, ] [package.dependencies] @@ -1753,13 +1734,13 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.35.0" +version = "2.36.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"}, - {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"}, + {file = "google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb"}, + {file = "google_auth-2.36.0.tar.gz", hash = "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1"}, ] [package.dependencies] @@ -3643,13 +3624,13 @@ files = [ [[package]] name = "pydata-sphinx-theme" -version = "0.15.4" +version = "0.16.0" description = "Bootstrap-based Sphinx theme from the PyData community" optional = false python-versions = ">=3.9" files = [ - {file = "pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6"}, - {file = "pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d"}, + {file = "pydata_sphinx_theme-0.16.0-py3-none-any.whl", hash = "sha256:18c810ee4e67e05281e371e156c1fb5bb0fa1f2747240461b225272f7d8d57d8"}, + {file = "pydata_sphinx_theme-0.16.0.tar.gz", hash = "sha256:721dd26e05fa8b992d66ef545536e6cbe0110afb9865820a08894af1ad6f7707"}, ] [package.dependencies] @@ -3657,9 +3638,8 @@ accessible-pygments = "*" Babel = "*" beautifulsoup4 = "*" docutils = "!=0.17.0" -packaging = "*" pygments = ">=2.7" -sphinx = ">=5" +sphinx = ">=6.1" typing-extensions = "*" [package.extras] @@ -4151,114 +4131,101 @@ files = [ [[package]] name = "rpds-py" -version = "0.20.0" +version = "0.21.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, - {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, - {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, - {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, - {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, - {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, - {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, - {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, - {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, - {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, - {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, - {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, - {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, - {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, - {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, + {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, + {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, + {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, + {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, + {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, + {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, + {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, + {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed"}, + {file = "rpds_py-0.21.0-cp39-none-win32.whl", hash = "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8"}, + {file = "rpds_py-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89"}, + {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, ] [[package]] @@ -4761,13 +4728,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.6" +version = "4.67.0" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.6-py3-none-any.whl", hash = "sha256:223e8b5359c2efc4b30555531f09e9f2f3589bcd7fdd389271191031b49b7a63"}, - {file = "tqdm-4.66.6.tar.gz", hash = "sha256:4bdd694238bef1485ce839d67967ab50af8f9272aab687c0d7702a01da0be090"}, + {file = "tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be"}, + {file = "tqdm-4.67.0.tar.gz", hash = "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a"}, ] [package.dependencies] @@ -4775,6 +4742,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +discord = ["requests"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] diff --git a/pyproject.toml b/pyproject.toml index d8917eac09..822a257ed9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -153,6 +153,8 @@ module = [ "scipy.optimize", "ansys.mapdl", "ansys.mapdl.core", + "ansys.mechanical", + "ansys.mechanical.*", "ansys.dpf.core", "ansys.dpf.core.core", "ansys.dpf.core.*", diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index b61c0ba2b1..e684e05728 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -27,7 +27,7 @@ import importlib.metadata -from . import material_property_sets +from . import extras, material_property_sets, mechanical_integration_helpers from ._model_printer import get_model_tree, print_model from ._plotter import get_directions_plotter from ._recursive_copy import LinkedObjectHandling, recursive_copy @@ -237,6 +237,7 @@ "ElementSetNodalData", "ElementTechnology", "ExportSettings", + "extras", "ExtrusionGuide", "ExtrusionGuideType", "ExtrusionMethod", @@ -284,6 +285,7 @@ "LookUpTableColumnValueType", "material_property_sets", "Material", + "mechanical_integration_helpers", "MeshData", "MeshImportType", "Model", @@ -294,7 +296,6 @@ "ModelingPlyNodalData", "ModelNodalData", "NodalDataType", - "SolidModelOffsetDirectionType", "OffsetType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", @@ -304,6 +305,7 @@ "ParallelSelectionRuleNodalData", "PlyCutoffType", "PlyGeometryExportFormat", + "SolidModelOffsetDirectionType", "PlyType", "PrimaryPly", "print_model", diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index caafe4acb3..645bd0ef6b 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -59,6 +59,7 @@ class ExampleKeys(Enum): THICKNESS_GEOMETRY = auto() MINIMAL_FLAT_PLATE = auto() OPTIMIZATION_EXAMPLE_DAT = auto() + CLASS40_AGDB = auto() EXAMPLE_FILES: dict[ExampleKeys, _ExampleLocation] = { @@ -89,6 +90,7 @@ class ExampleKeys(Enum): ExampleKeys.OPTIMIZATION_EXAMPLE_DAT: _ExampleLocation( directory="optimization_example", filename="optimization_model.dat" ), + ExampleKeys.CLASS40_AGDB: _ExampleLocation(directory="class40", filename="class40.agdb"), } diff --git a/src/ansys/acp/core/mechanical_integration_helpers.py b/src/ansys/acp/core/mechanical_integration_helpers.py new file mode 100644 index 0000000000..0c9e8810de --- /dev/null +++ b/src/ansys/acp/core/mechanical_integration_helpers.py @@ -0,0 +1,173 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Helper functions for exchanging data between PyACP and PyMechanical.""" + +import pathlib +import shutil +import textwrap +import typing + +if typing.TYPE_CHECKING: + import ansys.mechanical.core as pymechanical + +from ._typing_helper import PATH + +__all__ = [ + "export_mesh_for_acp", + "import_acp_composite_definitions", + "import_acp_solid_mesh", +] + + +def export_mesh_for_acp(*, mechanical: "pymechanical.Mechanical", path: PATH) -> None: + """Export the mesh from PyMechanical for use in PyACP. + + Parameters + ---------- + mechanical : + The PyMechanical instance. This must be a remote instance. + path : + The path to save the mesh to. The extension must be '.h5'. + """ + path = pathlib.Path(path) + + if path.suffix != ".h5": + raise ValueError(f"The output path extension must be '.h5', not '{path.suffix}'.") + output_path_str = str(path) + mechanical.run_python_script( + textwrap.dedent( + f"""\ + geometry_type = Ansys.Mechanical.DataModel.Enums.GeometryType.Sheet + unit = Ansys.Mechanical.DataModel.Enums.WBUnitSystemType.ConsistentMKS + dsid = 0 + + Model.InternalObject.WriteHDF5TransferFile(geometry_type, {output_path_str!r}, unit, dsid) + """ + ) + ) + + +def import_acp_solid_mesh(*, mechanical: "pymechanical.Mechanical", cdb_path: PATH) -> None: + """Import an ACP solid model into Mechanical. + + Import a solid mesh in CDB format into Mechanical. This function does not + import the ACP layup definition, use :func:`import_acp_composite_definitions` + for this purpose. + + .. warning:: + + The named selections exported from ACP are only partially imported. + + Parameters + ---------- + mechanical : + The PyMechanical instance. This must be a remote instance. + cdb_path : + The path of the CDB file to import. The extension must be '.cdb'. + """ + cdb_path = pathlib.Path(cdb_path) + + if cdb_path.suffix != ".cdb": + raise ValueError(f"The CDB file extension must be '.cdb', not '{cdb_path.suffix}'.") + cdb_path_str = str(cdb_path) + + mechanical.run_python_script( + textwrap.dedent( + f"""\ + initial_geometry_ids = {{obj.ObjectId for obj in Model.Geometry.Children}} + + model_import = Model.AddGeometryImportGroup().AddModelImport() + model_import.ModelImportSourceFilePath = {cdb_path_str!r} + model_import.ProcessValidBlockedCDBFile = False + model_import.ProcessModelData = False + model_import.Import() + + final_geometry_ids = {{obj.ObjectId for obj in Model.Geometry.Children}} + new_geometry_ids = final_geometry_ids - initial_geometry_ids + if len(new_geometry_ids) != 1: + raise ValueError("Expected 1 new geometry object, but found {{}}.".format(len(new_geometry_ids))) + new_geometry_id = new_geometry_ids.pop() + + new_geometry = DataModel.GetObjectById(new_geometry_id) + if len(new_geometry.Children) != 1: + raise ValueError("Expected 1 body, but got {{}}.".format(len(new_geometry.Children))) + + body = new_geometry.GetChildren( + Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True + )[0] + body.AddCommandSnippet().Input = "keyo,matid,3,1\\nkeyo,matid,8,1" + """ + ) + ) + + +def import_acp_composite_definitions(*, mechanical: "pymechanical.Mechanical", path: PATH) -> None: + """Import ACP composite definitions HDF5 into Mechanical. + + Imports the composite layup defined in ACP into Mechanical, as Imported Plies. + + This function does not import the solid mesh, use :func:`import_acp_solid_mesh` + for this purpose. + + Parameters + ---------- + mechanical : + The PyMechanical instance. This must be a remote instance. + path : + The path of the file to import. The extension must be '.h5'. + """ + path = pathlib.Path(path) + setup_folder_name = "Setup" + + if path.suffix != ".h5": + raise ValueError( + f"The composite definitions file extension must be '.h5', not '{path.suffix}'." + ) + + setup_folder = pathlib.Path(mechanical.project_directory) / setup_folder_name + setup_folder.mkdir(exist_ok=True) + target_path = setup_folder / path.name + try: + shutil.copyfile(path, target_path) + except shutil.SameFileError: + pass + + target_path_str = f"{setup_folder_name}::{target_path.resolve()}".replace("\\", "\\\\") + + mechanical.run_python_script( + textwrap.dedent( + f"""\ + import clr + clr.AddReference("Ansys.Common.Interop.{mechanical.version}") + composite_definition_paths_coll = Ansys.Common.Interop.AnsCoreObjects.AnsBSTRColl() + composite_definition_paths_coll.Add({target_path_str!r}) + mapping_paths_coll = Ansys.Common.Interop.AnsCoreObjects.AnsVARIANTColl() + mapping_paths_coll.Add(None) + external_model = Model.InternalObject.AddExternalEnhancedModel( + Ansys.Common.Interop.DSObjectTypes.DSExternalEnhancedModelType.kEXTERNAL_ENHANCEDMODEL_LAYEREDSECTION + ) + external_model.Import(composite_definition_paths_coll, mapping_paths_coll) + external_model.Update() + """ + ) + ) From f9a2f3af8b87857649ab8f26813f61f775fc2d44 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 20 Nov 2024 16:07:32 +0100 Subject: [PATCH 65/96] Split examples into groups (#684) Split the examples into different groups for modeling features, complete workflows, and use cases. Each group is a direct subdirectory of `doc/source/examples`. The consequence of this is that the directory name needs to be repeated: - in the `conf.py` - in the `make.bat` and `Makefile` for the `clean` target This could be avoided by introducing an additional nesting level, at the cost of making the resulting URLs uglier. The individual example filenames are changed to be more descriptive, addressing #527. --- doc/Makefile | 6 ++- doc/make.bat | 6 ++- .../_static/gallery_thumbnails/README.md | 21 ++++++--- ..._03-pymechanical-shell-workflow_thumb.png} | Bin ..._04-pymechanical-solid-workflow_thumb.png} | Bin ...05-pymechanical-to-cdb-workflow_thumb.png} | Bin doc/source/conf.py | 31 +++++++++---- doc/source/examples/.gitignore | 4 +- doc/source/examples/index.rst | 41 ++++++++++-------- .../user_guide/howto/create_input_file.rst | 6 +-- .../01-sandwich-panel-layup.py} | 8 ++-- .../02-simple-selection-rules.py} | 6 +-- .../03-advanced-selection-rules.py} | 10 ++--- .../04-layup-thickness-definitions.py} | 2 +- .../05-rosettes-ply-directions.py} | 2 +- .../06-ply-direction-lookup-table.py} | 3 +- examples/{ => modeling_features}/README.rst | 0 .../01-optimizing-ply-angles.py} | 4 +- examples/use_cases/README.rst | 1 + .../01-pymapdl-workflow.py} | 6 +-- .../02-advanced-pymapdl-workflow.py} | 6 +-- .../03-pymechanical-shell-workflow.py} | 2 +- .../04-pymechanical-solid-workflow.py} | 2 +- .../05-pymechanical-to-cdb-workflow.py} | 3 +- examples/workflows/README.rst | 0 25 files changed, 105 insertions(+), 65 deletions(-) rename doc/source/_static/gallery_thumbnails/{sphx_glr_010_pymechanical_shell_workflow_thumb.png => sphx_glr_03-pymechanical-shell-workflow_thumb.png} (100%) rename doc/source/_static/gallery_thumbnails/{sphx_glr_011_pymechanical_solid_workflow_thumb.png => sphx_glr_04-pymechanical-solid-workflow_thumb.png} (100%) rename doc/source/_static/gallery_thumbnails/{sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png => sphx_glr_05-pymechanical-to-cdb-workflow_thumb.png} (100%) rename examples/{002_sandwich_panel.py => modeling_features/01-sandwich-panel-layup.py} (97%) rename examples/{003_basic_rules.py => modeling_features/02-simple-selection-rules.py} (96%) rename examples/{004_advanced_rules.py => modeling_features/03-advanced-selection-rules.py} (98%) rename examples/{005_thickness_definitions.py => modeling_features/04-layup-thickness-definitions.py} (98%) rename examples/{006_rosettes.py => modeling_features/05-rosettes-ply-directions.py} (99%) rename examples/{007_direction_definitions.py => modeling_features/06-ply-direction-lookup-table.py} (99%) rename examples/{ => modeling_features}/README.rst (100%) rename examples/{009_optimization.py => use_cases/01-optimizing-ply-angles.py} (99%) create mode 100644 examples/use_cases/README.rst rename examples/{001_basic_flat_plate.py => workflows/01-pymapdl-workflow.py} (99%) rename examples/{008_solve_class40.py => workflows/02-advanced-pymapdl-workflow.py} (99%) rename examples/{010_pymechanical_shell_workflow.py => workflows/03-pymechanical-shell-workflow.py} (99%) rename examples/{011_pymechanical_solid_workflow.py => workflows/04-pymechanical-solid-workflow.py} (99%) rename examples/{012_pymechanical_to_cdb_workflow.py => workflows/05-pymechanical-to-cdb-workflow.py} (99%) create mode 100644 examples/workflows/README.rst diff --git a/doc/Makefile b/doc/Makefile index 852468f0e3..efb413bfe0 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -28,5 +28,9 @@ pdf: # customized clean due to examples gallery clean: rm -rf $(BUILDDIR) - rm -rf $(SOURCEDIR)/examples/gallery_examples + rm -rf $(SOURCEDIR)/examples/images + rm -rf $(SOURCEDIR)/examples/modeling_features + rm -rf $(SOURCEDIR)/examples/use_cases + rm -rf $(SOURCEDIR)/examples/workflows + rm -rf $(SOURCEDIR)/examples/sg_execution_times.rst find . -type d -name "_autosummary" -exec rm -rf {} + diff --git a/doc/make.bat b/doc/make.bat index cc0f85c6bb..d3cbb56b3e 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -35,7 +35,11 @@ goto end :clean rmdir /s /q %BUILDDIR% > /NUL 2>&1 for /d /r %SOURCEDIR% %%d in (_autosummary,_gallery_backreferences) do @if exist "%%d" rmdir /s /q "%%d" -rmdir /s /q %SOURCEDIR%\examples\gallery_examples +rmdir /s /q %SOURCEDIR%\examples\images +rmdir /s /q %SOURCEDIR%\examples\modeling_features +rmdir /s /q %SOURCEDIR%\examples\use_cases +rmdir /s /q %SOURCEDIR%\examples\workflows +del %SOURCEDIR%\examples\sg_execution_times.rst goto end :pdf diff --git a/doc/source/_static/gallery_thumbnails/README.md b/doc/source/_static/gallery_thumbnails/README.md index 69a8b8e96c..ef35fe7775 100644 --- a/doc/source/_static/gallery_thumbnails/README.md +++ b/doc/source/_static/gallery_thumbnails/README.md @@ -2,9 +2,18 @@ This directory contains thumbnails for the gallery examples which are not built in CI. The thumbnails are used in the gallery index page. To update a thumbnail: -- remove the ``# sphinx_gallery_thumbnail_path`` configuration in the - example file -- build the documentation, running the example that you want to update -- copy the generated thumbnail from the example directory to this one -- re-add the ``# sphinx_gallery_thumbnail_path`` configuration in the - example file +- Remove the ``# sphinx_gallery_thumbnail_path`` configuration in the + example file. +- Build the documentation, running the example that you want to update. + You may need to add a configuration option like + ``# sphinx_gallery_thumbnail_number = -1`` to select the image which should + be used as thumbnail. [1] +- Copy the generated thumbnail from the example directory to this one. +- Re-add the ``# sphinx_gallery_thumbnail_path`` configuration in the + example file. + +NOTE: If the example file has been renamed since the last time the thumbnail +was generated, the thumbnail filename will also be different. + +[1] These options conflict with ``# sphinx_gallery_thumbnail_path``, so they +cannot be added permanently to the example file. diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_010_pymechanical_shell_workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_03-pymechanical-shell-workflow_thumb.png similarity index 100% rename from doc/source/_static/gallery_thumbnails/sphx_glr_010_pymechanical_shell_workflow_thumb.png rename to doc/source/_static/gallery_thumbnails/sphx_glr_03-pymechanical-shell-workflow_thumb.png diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_011_pymechanical_solid_workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_04-pymechanical-solid-workflow_thumb.png similarity index 100% rename from doc/source/_static/gallery_thumbnails/sphx_glr_011_pymechanical_solid_workflow_thumb.png rename to doc/source/_static/gallery_thumbnails/sphx_glr_04-pymechanical-solid-workflow_thumb.png diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_05-pymechanical-to-cdb-workflow_thumb.png similarity index 100% rename from doc/source/_static/gallery_thumbnails/sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png rename to doc/source/_static/gallery_thumbnails/sphx_glr_05-pymechanical-to-cdb-workflow_thumb.png diff --git a/doc/source/conf.py b/doc/source/conf.py index 7c666c1c37..eb4b81cd8d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -3,6 +3,7 @@ from datetime import datetime import inspect import os +import pathlib import sys import warnings @@ -178,10 +179,7 @@ def _signature( "sphinx.ext.napoleon", "numpydoc", "sphinx_copybutton", -] -if not SKIP_GALLERY: - extensions += ["sphinx_gallery.gen_gallery"] -extensions += [ + "sphinx_gallery.gen_gallery", "sphinx_design", # needed for pyvista offlineviewer directive "sphinx_jinja", "pyvista.ext.plot_directive", @@ -263,18 +261,33 @@ def _signature( r".*\.EdgePropertyList\.clear", } +if SKIP_GALLERY: + # Generate the gallery without executing the code. The gallery will not + # contain the output of the code cells. + # This is useful for more quickly building the documentation. + gallery_filename_pattern = "" +else: + if sys.platform == "win32": + gallery_filename_pattern = r".*\.py" + else: + gallery_filename_pattern = ( + r"^(?!.*pymechanical.*\.py).*\.py" # skip pymechanical examples on non-Windows + ) + +examples_dirs_base = pathlib.Path("../../examples/") +gallery_dirs_base = pathlib.Path("examples/") +example_subdir_names = ["modeling_features", "workflows", "use_cases"] + # sphinx gallery options sphinx_gallery_conf = { # convert rst to md for ipynb "pypandoc": True, # path to your examples scripts - "examples_dirs": ["../../examples/"], + "examples_dirs": [str(examples_dirs_base / subdir) for subdir in example_subdir_names], # path where to save gallery generated examples - "gallery_dirs": ["examples/gallery_examples"], + "gallery_dirs": [str(gallery_dirs_base / subdir) for subdir in example_subdir_names], # Pattern to search for example files - "filename_pattern": ( - r".*\.py" if sys.platform == "win32" else r"^(?!.*pymechanical.*\.py).*\.py" - ), # execute PyMechanical examples only on Windows + "filename_pattern": gallery_filename_pattern, # Remove the "Download all examples" button from the top level gallery "download_all_examples": False, # Sort gallery example by filename instead of number of lines (default) diff --git a/doc/source/examples/.gitignore b/doc/source/examples/.gitignore index 718bd55d42..86693017f6 100644 --- a/doc/source/examples/.gitignore +++ b/doc/source/examples/.gitignore @@ -1 +1,3 @@ -gallery_examples +* +!.gitignore +!./index.rst diff --git a/doc/source/examples/index.rst b/doc/source/examples/index.rst index d6623ccdb5..3c63d7d056 100644 --- a/doc/source/examples/index.rst +++ b/doc/source/examples/index.rst @@ -1,25 +1,32 @@ .. _ref_examples: -.. - Add links to the gallery examples which would otherwise cause a warning due - to missing references +======== +Examples +======== -.. jinja:: conditional_skip +ACP modeling features +===================== - {% if skip_gallery %} - .. _sphx_glr_examples_gallery_examples_001_basic_flat_plate.py: +These examples show how to use PyACP for defining composite layups. - {% endif %} +.. include:: modeling_features/index.rst + :start-line: 2 - ======== - Examples - ======== - {% if not skip_gallery %} - .. include:: gallery_examples/index.rst - :start-line: 2 - {% else %} - .. note:: +Workflow examples +================= - The gallery examples are not included in this build of the documentation. - {% endif %} +These examples show how to combine PyACP with other tools to create a full +simulation workflow. + +.. include:: workflows/index.rst + :start-line: 2 + +Use case examples +================= + +These examples can serve as an inspiration for how you can tackle your own +use cases with PyACP. + +.. include:: use_cases/index.rst + :start-line: 2 diff --git a/doc/source/user_guide/howto/create_input_file.rst b/doc/source/user_guide/howto/create_input_file.rst index 283c7683f2..e0a9ae67c6 100644 --- a/doc/source/user_guide/howto/create_input_file.rst +++ b/doc/source/user_guide/howto/create_input_file.rst @@ -9,7 +9,7 @@ edge sets, and materials from the input file. Once the layup has been created wi contains all the data from the initial input file along with the layup information and materials added by PyACP. An attempt is made to preserve the original input file as much as possible. This includes the original mesh, materials, and boundary conditions. Therefore, you may directly use the exported CDB file -for an analysis through PyMAPDL. For more information, see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +for an analysis through PyMAPDL. For more information, see :ref:`pymapdl_workflow_example`. .. _input_file_from_mechanical: @@ -29,7 +29,7 @@ One way to create an input file for PyACP is to create a static structural setup The created input file can be read with the :meth:`.ACPWorkflow.from_cdb_or_dat_file` method. -For a complete example, see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +For a complete example, see :ref:`pymapdl_workflow_example`. .. note:: @@ -50,7 +50,7 @@ You can also create an input file for PyACP by performing these steps: CDWRITE,ALL,FILE,cdbfile.cdb The created input file can be read with :meth:`.ACPWorkflow.from_cdb_or_dat_file`. See -:ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py` for a complete example. +:ref:`pymapdl_workflow_example` for a complete example. Notes on material handling ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/002_sandwich_panel.py b/examples/modeling_features/01-sandwich-panel-layup.py similarity index 97% rename from examples/002_sandwich_panel.py rename to examples/modeling_features/01-sandwich-panel-layup.py index 324731bdb5..6679f921af 100644 --- a/examples/002_sandwich_panel.py +++ b/examples/modeling_features/01-sandwich-panel-layup.py @@ -21,14 +21,14 @@ # SOFTWARE. """ -.. _basic_sandwich_panel: +.. _sandwich_panel: -Basic sandwich panel example -============================ +Sandwich panel example +====================== This example defines a composite lay-up for a sandwich panel using PyACP. It only shows the PyACP part of the setup. For a complete composite analysis, -see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +see :ref:`pymapdl_workflow_example`. """ # %%# Import modules diff --git a/examples/003_basic_rules.py b/examples/modeling_features/02-simple-selection-rules.py similarity index 96% rename from examples/003_basic_rules.py rename to examples/modeling_features/02-simple-selection-rules.py index 269315ab1b..157827f158 100644 --- a/examples/003_basic_rules.py +++ b/examples/modeling_features/02-simple-selection-rules.py @@ -21,7 +21,7 @@ # SOFTWARE. """ -.. _basic_rules_example: +.. _basic_selection_rules_example: Basic selection rules example ============================= @@ -29,8 +29,8 @@ This example shows the basic usage of selection rules, which enable you to select elements through geometrical operations and thus to shape plies. The example only shows the PyACP part of the setup. For more advanced selection rule usage, see -:ref:`sphx_glr_examples_gallery_examples_004_advanced_rules.py`. For a complete composite -analysis, see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +:ref:`advanced_selection_rules_example`. For a complete composite +analysis, see :ref:`pymapdl_workflow_example`. """ diff --git a/examples/004_advanced_rules.py b/examples/modeling_features/03-advanced-selection-rules.py similarity index 98% rename from examples/004_advanced_rules.py rename to examples/modeling_features/03-advanced-selection-rules.py index 1314dfac37..8989b257ad 100644 --- a/examples/004_advanced_rules.py +++ b/examples/modeling_features/03-advanced-selection-rules.py @@ -21,18 +21,18 @@ # SOFTWARE. """ -.. _advanced_rules_example: +.. _advanced_selection_rules_example: -Advanced rules example -====================== +Advanced selection rules example +================================ This example shows how to use advanced rules, including the geometrical, cut-off, and variable offset rules. It also demonstrates how rules can be templated and reused with different parameters. For more basic rules, see -:ref:`sphx_glr_examples_gallery_examples_003_basic_rules.py`. +:ref:`basic_selection_rules_example`. This example only shows the PyACP part of the setup. For a complete composite analysis, -see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +see :ref:`pymapdl_workflow_example`. """ diff --git a/examples/005_thickness_definitions.py b/examples/modeling_features/04-layup-thickness-definitions.py similarity index 98% rename from examples/005_thickness_definitions.py rename to examples/modeling_features/04-layup-thickness-definitions.py index 85698928c8..4734dbbb94 100644 --- a/examples/005_thickness_definitions.py +++ b/examples/modeling_features/04-layup-thickness-definitions.py @@ -28,7 +28,7 @@ This example shows how the thickness of a ply can be defined by a geometry or a lookup table. The example only shows the PyACP part of the setup. For a complete composite analysis, -see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +see :ref:`pymapdl_workflow_example`. """ diff --git a/examples/006_rosettes.py b/examples/modeling_features/05-rosettes-ply-directions.py similarity index 99% rename from examples/006_rosettes.py rename to examples/modeling_features/05-rosettes-ply-directions.py index 0255728981..cdef2889f1 100644 --- a/examples/006_rosettes.py +++ b/examples/modeling_features/05-rosettes-ply-directions.py @@ -28,7 +28,7 @@ This example illustrates how you can use rosettes to define the reference directions of a ply. It only shows the PyACP part of the setup. For a complete composite analysis, -see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +see :ref:`pymapdl_workflow_example`. """ # %% diff --git a/examples/007_direction_definitions.py b/examples/modeling_features/06-ply-direction-lookup-table.py similarity index 99% rename from examples/007_direction_definitions.py rename to examples/modeling_features/06-ply-direction-lookup-table.py index 4102d9cbfd..5c07777801 100644 --- a/examples/007_direction_definitions.py +++ b/examples/modeling_features/06-ply-direction-lookup-table.py @@ -25,10 +25,11 @@ Direction definition example ============================ + This example shows how to define directions from lookup tables. They can be either reference directions for oriented selection sets or draping angles for modeling plies. The example only shows the PyACP part of the setup. For a complete composite analysis, -see :ref:`sphx_glr_examples_gallery_examples_001_basic_flat_plate.py`. +see :ref:`pymapdl_workflow_example`. """ # %% diff --git a/examples/README.rst b/examples/modeling_features/README.rst similarity index 100% rename from examples/README.rst rename to examples/modeling_features/README.rst diff --git a/examples/009_optimization.py b/examples/use_cases/01-optimizing-ply-angles.py similarity index 99% rename from examples/009_optimization.py rename to examples/use_cases/01-optimizing-ply-angles.py index 6d1f6525f4..7c95254fcc 100644 --- a/examples/009_optimization.py +++ b/examples/use_cases/01-optimizing-ply-angles.py @@ -23,8 +23,8 @@ """ .. _optimization_example: -Optimization example -==================== +Optimizing ply angles +===================== This example demonstrates how to use the ACP, MAPDL, and DPF servers to optimize the ply angles in a composite lay-up. The optimization aims to minimize the maximum inverse diff --git a/examples/use_cases/README.rst b/examples/use_cases/README.rst new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/examples/use_cases/README.rst @@ -0,0 +1 @@ + diff --git a/examples/001_basic_flat_plate.py b/examples/workflows/01-pymapdl-workflow.py similarity index 99% rename from examples/001_basic_flat_plate.py rename to examples/workflows/01-pymapdl-workflow.py index 9c21421df8..5da9a93c67 100644 --- a/examples/001_basic_flat_plate.py +++ b/examples/workflows/01-pymapdl-workflow.py @@ -21,10 +21,10 @@ # SOFTWARE. """ -.. _basic_flat_plate: +.. _pymapdl_workflow_example: -Basic PyACP workflow example -============================ +PyMAPDL workflow +================ This example shows how to define a composite lay-up with PyACP, solve the resulting model with PyMAPDL, and run a failure analysis with PyDPF Composites. diff --git a/examples/008_solve_class40.py b/examples/workflows/02-advanced-pymapdl-workflow.py similarity index 99% rename from examples/008_solve_class40.py rename to examples/workflows/02-advanced-pymapdl-workflow.py index 2f609ab02b..f0c81ca99b 100644 --- a/examples/008_solve_class40.py +++ b/examples/workflows/02-advanced-pymapdl-workflow.py @@ -21,10 +21,10 @@ # SOFTWARE. """ -.. _solve_class40_example: +.. _advanced_pymapdl_workflow_example: -Class 40 example -================ +Advanced PyMAPDL workflow +========================= This example shows how to define a composite lay-up with PyACP, solve the resulting model with PyMAPDL, and run a failure analysis with PyDPF Composites. diff --git a/examples/010_pymechanical_shell_workflow.py b/examples/workflows/03-pymechanical-shell-workflow.py similarity index 99% rename from examples/010_pymechanical_shell_workflow.py rename to examples/workflows/03-pymechanical-shell-workflow.py index 79169cf3e1..27df1da012 100644 --- a/examples/010_pymechanical_shell_workflow.py +++ b/examples/workflows/03-pymechanical-shell-workflow.py @@ -67,7 +67,7 @@ import ansys.dpf.composites as pydpf_composites import ansys.mechanical.core as pymechanical -# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_010_pymechanical_shell_workflow_thumb.png' +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_03-pymechanical-shell-workflow_thumb.png' # %% # Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` diff --git a/examples/011_pymechanical_solid_workflow.py b/examples/workflows/04-pymechanical-solid-workflow.py similarity index 99% rename from examples/011_pymechanical_solid_workflow.py rename to examples/workflows/04-pymechanical-solid-workflow.py index 1abca53159..83234b356d 100644 --- a/examples/011_pymechanical_solid_workflow.py +++ b/examples/workflows/04-pymechanical-solid-workflow.py @@ -70,7 +70,7 @@ import ansys.dpf.composites as pydpf_composites import ansys.mechanical.core as pymechanical -# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_011_pymechanical_solid_workflow_thumb.png' +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_04-pymechanical-solid-workflow_thumb.png' # %% # Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` diff --git a/examples/012_pymechanical_to_cdb_workflow.py b/examples/workflows/05-pymechanical-to-cdb-workflow.py similarity index 99% rename from examples/012_pymechanical_to_cdb_workflow.py rename to examples/workflows/05-pymechanical-to-cdb-workflow.py index 29af5e8265..17a4029df2 100644 --- a/examples/012_pymechanical_to_cdb_workflow.py +++ b/examples/workflows/05-pymechanical-to-cdb-workflow.py @@ -61,8 +61,7 @@ import ansys.mapdl.core as pymapdl import ansys.mechanical.core as pymechanical -# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_012_pymechanical_to_cdb_workflow_thumb.png' -# sphinx_gallery_thumbnail_number = -1 +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_05-pymechanical-to-cdb-workflow_thumb.png' # %% # Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` diff --git a/examples/workflows/README.rst b/examples/workflows/README.rst new file mode 100644 index 0000000000..e69de29bb2 From a72e2db4f91aba9dfb67971e89b724f9bc7f488a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:21:57 +0100 Subject: [PATCH 66/96] Remove mesh data objects from top level interface (#686) * move mesh data into separate namespace * add doc/create_doc_windows.ps1 to build doc on windows in combination with docker containers. --- README.rst | 5 + doc/create_doc_windows.ps1 | 30 +++++ doc/source/api/mesh_data.rst | 2 +- doc/source/conf.py | 4 +- src/ansys/acp/core/__init__.py | 79 +------------ src/ansys/acp/core/_plotter.py | 4 +- .../core/_tree_objects/field_definition.py | 5 +- src/ansys/acp/core/_tree_objects/model.py | 1 - src/ansys/acp/core/mesh_data.py | 108 ++++++++++++++++++ tests/unittests/test_analysis_ply.py | 3 +- .../unittests/test_boolean_selection_rule.py | 5 +- tests/unittests/test_cutoff_selection_rule.py | 8 +- .../test_cylindrical_selection_rule.py | 5 +- .../test_geometrical_selection_rule.py | 4 +- tests/unittests/test_model.py | 3 +- tests/unittests/test_modeling_ply.py | 2 +- .../unittests/test_parallel_selection_rule.py | 4 +- tests/unittests/test_production_ply.py | 3 +- .../test_spherical_selection_rule.py | 5 +- tests/unittests/test_tube_selection_rule.py | 2 +- .../test_variable_offset_selection_rule.py | 2 +- type_checks/plots.py | 4 +- 22 files changed, 177 insertions(+), 111 deletions(-) create mode 100644 doc/create_doc_windows.ps1 create mode 100644 src/ansys/acp/core/mesh_data.py diff --git a/README.rst b/README.rst index 0eba319ae2..61b93afbd0 100644 --- a/README.rst +++ b/README.rst @@ -208,6 +208,11 @@ valid license server (e.g ``1055@mylicenseserver.com``). Then start the docker c Then build the documentation with the `Sphinx`_ commands mentioned above. +On Windows, you can use the shipped shell script: + +.. code-block:: batch + + .\doc\create_doc_windows.ps1 Distribution ^^^^^^^^^^^^ diff --git a/doc/create_doc_windows.ps1 b/doc/create_doc_windows.ps1 new file mode 100644 index 0000000000..404e0570a7 --- /dev/null +++ b/doc/create_doc_windows.ps1 @@ -0,0 +1,30 @@ +if ($Env:ANSYSLMD_LICENSE_FILE -ne $null) +{ + "ANSYSLMD_LICENSE_FILE=" + $Env:ANSYSLMD_LICENSE_FILE +} +else +{ + "Env variable 'ANSYSLMD_LICENSE_FILE' is required for the license checks." + "Example: ANSYSLMD_LICENSE_FILE='1055@my_license_server'" + exit 1 +} + +docker pull ghcr.io/ansys/pydpf-composites:latest +docker pull ghcr.io/ansys/mapdl:latest + +$Env:ANSYS_DPF_ACCEPT_LA="Y" +$Env:PYMAPDL_PORT=59991 +$Env:PYMAPDL_START_INSTANCE="FALSE" +$Env:PYDPF_COMPOSITES_DOCKER_CONTAINER_PORT=59992 +$Env:SPHINXOPT_NITPICKY=0 +# whether to skip the gallery (examples) +$Env:PYACP_DOC_SKIP_GALLERY=0 +# whether to skip the API documentation +$Env:PYACP_DOC_SKIP_API=0 + +docker-compose -f ./docker-compose/docker-compose-extras.yaml up -d + +cd doc +.\make.bat html +cd .. +docker-compose -f ./docker-compose/docker-compose-extras.yaml down diff --git a/doc/source/api/mesh_data.rst b/doc/source/api/mesh_data.rst index ad79479215..1423ace5d6 100644 --- a/doc/source/api/mesh_data.rst +++ b/doc/source/api/mesh_data.rst @@ -1,7 +1,7 @@ Mesh data objects ----------------- -.. currentmodule:: ansys.acp.core +.. currentmodule:: ansys.acp.core.mesh_data .. autosummary:: :toctree: _autosummary diff --git a/doc/source/conf.py b/doc/source/conf.py index eb4b81cd8d..487f1da097 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -38,11 +38,8 @@ def _signature( BooleanSelectionRule, CADComponent, GeometricalSelectionRule, - MeshData, Model, ModelingGroup, - ScalarData, - VectorData, ) # Import type aliases so that they can be resolved correctly. @@ -58,6 +55,7 @@ def _signature( from ansys.acp.core._tree_objects.sensor import _LINKABLE_ENTITY_TYPES # noqa: F401 from ansys.acp.core._tree_objects.sublaminate import _LINKABLE_MATERIAL_TYPES # noqa: F401 from ansys.acp.core._typing_helper import StrEnum + from ansys.acp.core.mesh_data import MeshData, ScalarData, VectorData # noqa: F401 from ansys.dpf.composites.data_sources import ContinuousFiberCompositesFiles # noqa: F401 from ansys.dpf.core import UnitSystem # noqa: F401 import ansys.mechanical.core as pymechanical # noqa: F401 diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index e684e05728..0407a519e6 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -27,7 +27,7 @@ import importlib.metadata -from . import extras, material_property_sets, mechanical_integration_helpers +from . import extras, material_property_sets, mechanical_integration_helpers, mesh_data from ._model_printer import get_model_tree, print_model from ._plotter import get_directions_plotter from ._recursive_copy import LinkedObjectHandling, recursive_copy @@ -41,14 +41,10 @@ ) from ._tree_objects import ( AnalysisPly, - AnalysisPlyElementalData, - AnalysisPlyNodalData, ArrowType, BaseElementMaterialHandling, BooleanOperationType, BooleanSelectionRule, - BooleanSelectionRuleElementalData, - BooleanSelectionRuleNodalData, ButtJointSequence, CADComponent, CADGeometry, @@ -58,11 +54,7 @@ CutoffMaterialHandling, CutoffRuleType, CutoffSelectionRule, - CutoffSelectionRuleElementalData, - CutoffSelectionRuleNodalData, CylindricalSelectionRule, - CylindricalSelectionRuleElementalData, - CylindricalSelectionRuleNodalData, DimensionType, DrapingMaterialModel, DrapingType, @@ -73,8 +65,6 @@ EdgeSetType, ElementalDataType, ElementSet, - ElementSetElementalData, - ElementSetNodalData, ElementTechnology, ExtrusionGuide, ExtrusionGuideType, @@ -86,8 +76,6 @@ FieldDefinition, GeometricalRuleType, GeometricalSelectionRule, - GeometricalSelectionRuleElementalData, - GeometricalSelectionRuleNodalData, HDF5CompositeCAEImportMode, HDF5CompositeCAEProjectionMode, IgnorableEntity, @@ -99,9 +87,7 @@ ImportedPlyThicknessType, ImportedProductionPly, ImportedSolidModel, - ImportedSolidModelElementalData, ImportedSolidModelExportSettings, - ImportedSolidModelNodalData, InterfaceLayer, IntersectionType, Lamina, @@ -115,36 +101,24 @@ LookUpTable3DInterpolationAlgorithm, LookUpTableColumnValueType, Material, - MeshData, MeshImportType, Model, - ModelElementalData, ModelingGroup, ModelingPly, - ModelingPlyElementalData, - ModelingPlyNodalData, - ModelNodalData, NodalDataType, OffsetType, OrientedSelectionSet, - OrientedSelectionSetElementalData, - OrientedSelectionSetNodalData, ParallelSelectionRule, - ParallelSelectionRuleElementalData, - ParallelSelectionRuleNodalData, PlyCutoffType, PlyGeometryExportFormat, PlyType, PrimaryPly, ProductionPly, - ProductionPlyElementalData, - ProductionPlyNodalData, ReinforcingBehavior, Rosette, RosetteSelectionMethod, RosetteType, SamplingPoint, - ScalarData, SectionCut, SectionCutType, Sensor, @@ -153,20 +127,14 @@ SnapToGeometry, SnapToGeometryOrientationType, SolidElementSet, - SolidElementSetElementalData, - SolidElementSetNodalData, SolidMappingProperties, SolidModel, - SolidModelElementalData, SolidModelExportFormat, SolidModelExportSettings, SolidModelImportFormat, - SolidModelNodalData, SolidModelOffsetDirectionType, SolidModelSkinExportFormat, SphericalSelectionRule, - SphericalSelectionRuleElementalData, - SphericalSelectionRuleNodalData, Stackup, Status, StressStateType, @@ -176,15 +144,9 @@ TaperEdge, ThicknessFieldType, ThicknessType, - TriangleMesh, TubeSelectionRule, - TubeSelectionRuleElementalData, - TubeSelectionRuleNodalData, UnitSystemType, VariableOffsetSelectionRule, - VariableOffsetSelectionRuleElementalData, - VariableOffsetSelectionRuleNodalData, - VectorData, VirtualGeometry, VirtualGeometryDimension, ) @@ -198,14 +160,10 @@ "ACPInstance", "ACPWorkflow", "AnalysisPly", - "AnalysisPlyElementalData", - "AnalysisPlyNodalData", "ArrowType", "BaseElementMaterialHandling", "BooleanOperationType", "BooleanSelectionRule", - "BooleanSelectionRuleElementalData", - "BooleanSelectionRuleNodalData", "ButtJointSequence", "CADComponent", "CADGeometry", @@ -216,11 +174,7 @@ "CutoffMaterialHandling", "CutoffRuleType", "CutoffSelectionRule", - "CutoffSelectionRuleElementalData", - "CutoffSelectionRuleNodalData", "CylindricalSelectionRule", - "CylindricalSelectionRuleElementalData", - "CylindricalSelectionRuleNodalData", "DimensionType", "DirectLaunchConfig", "DockerComposeLaunchConfig", @@ -233,8 +187,6 @@ "EdgeSetType", "ElementalDataType", "ElementSet", - "ElementSetElementalData", - "ElementSetNodalData", "ElementTechnology", "ExportSettings", "extras", @@ -248,8 +200,6 @@ "FieldDefinition", "GeometricalRuleType", "GeometricalSelectionRule", - "GeometricalSelectionRuleElementalData", - "GeometricalSelectionRuleNodalData", "get_composite_post_processing_files", "get_directions_plotter", "get_dpf_unit_system", @@ -265,9 +215,7 @@ "ImportedPlyThicknessType", "ImportedProductionPly", "ImportedSolidModel", - "ImportedSolidModelElementalData", "ImportedSolidModelExportSettings", - "ImportedSolidModelNodalData", "InterfaceLayer", "IntersectionType", "Lamina", @@ -286,23 +234,14 @@ "material_property_sets", "Material", "mechanical_integration_helpers", - "MeshData", "MeshImportType", "Model", - "ModelElementalData", "ModelingGroup", "ModelingPly", - "ModelingPlyElementalData", - "ModelingPlyNodalData", - "ModelNodalData", "NodalDataType", "OffsetType", "OrientedSelectionSet", - "OrientedSelectionSetElementalData", - "OrientedSelectionSetNodalData", "ParallelSelectionRule", - "ParallelSelectionRuleElementalData", - "ParallelSelectionRuleNodalData", "PlyCutoffType", "PlyGeometryExportFormat", "SolidModelOffsetDirectionType", @@ -310,15 +249,12 @@ "PrimaryPly", "print_model", "ProductionPly", - "ProductionPlyElementalData", - "ProductionPlyNodalData", "recursive_copy", "ReinforcingBehavior", "Rosette", "RosetteSelectionMethod", "RosetteType", "SamplingPoint", - "ScalarData", "SectionCut", "SectionCutType", "Sensor", @@ -327,19 +263,13 @@ "SnapToGeometry", "SnapToGeometryOrientationType", "SolidElementSet", - "SolidElementSetElementalData", - "SolidElementSetNodalData", "SolidMappingProperties", "SolidModel", - "SolidModelElementalData", "SolidModelExportFormat", "SolidModelExportSettings", "SolidModelImportFormat", - "SolidModelNodalData", "SolidModelSkinExportFormat", "SphericalSelectionRule", - "SphericalSelectionRuleElementalData", - "SphericalSelectionRuleNodalData", "Stackup", "Status", "StressStateType", @@ -349,15 +279,10 @@ "TaperEdge", "ThicknessFieldType", "ThicknessType", - "TriangleMesh", "TubeSelectionRule", - "TubeSelectionRuleElementalData", - "TubeSelectionRuleNodalData", "UnitSystemType", "VariableOffsetSelectionRule", - "VariableOffsetSelectionRuleElementalData", - "VariableOffsetSelectionRuleNodalData", - "VectorData", "VirtualGeometry", "VirtualGeometryDimension", + "mesh_data", ] diff --git a/src/ansys/acp/core/_plotter.py b/src/ansys/acp/core/_plotter.py index 409d112fe6..743d9c1e97 100644 --- a/src/ansys/acp/core/_plotter.py +++ b/src/ansys/acp/core/_plotter.py @@ -27,8 +27,8 @@ if TYPE_CHECKING: # pragma: no cover from ansys.acp.core import Model - from ansys.acp.core import MeshData - from ansys.acp.core import VectorData + from ansys.acp.core.mesh_data import MeshData + from ansys.acp.core.mesh_data import VectorData from ansys.acp.core._utils.visualization import _replace_underscores_and_capitalize diff --git a/src/ansys/acp/core/_tree_objects/field_definition.py b/src/ansys/acp/core/_tree_objects/field_definition.py index 43c5d6bd8f..dc70e4391c 100644 --- a/src/ansys/acp/core/_tree_objects/field_definition.py +++ b/src/ansys/acp/core/_tree_objects/field_definition.py @@ -64,10 +64,7 @@ class FieldDefinition(CreatableTreeObject, IdTreeObject): state of material field (e.g. degradation). The field definition allows to define the material field per element or per ply and element. - Note - ---- - - Field definitions are currently only supported through (Py)Mechanical. + Note: Field definitions are currently only supported through (Py)Mechanical. The direct interface of PyACP to (Py)MADL ignores field definitions. Parameters diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index bda55a80ed..79ac727c9f 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -141,7 +141,6 @@ "HDF5CompositeCAEImportMode", "HDF5CompositeCAEProjectionMode", "IgnorableEntity", - "MeshData", "Model", "ModelElementalData", "ModelNodalData", diff --git a/src/ansys/acp/core/mesh_data.py b/src/ansys/acp/core/mesh_data.py new file mode 100644 index 0000000000..1d235fcf33 --- /dev/null +++ b/src/ansys/acp/core/mesh_data.py @@ -0,0 +1,108 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Move mesh_data into separate namespace. + +Remove classes and function which are not used by the public API +into a separate namespace. +""" + +from ._tree_objects import ( + AnalysisPlyElementalData, + AnalysisPlyNodalData, + BooleanSelectionRuleElementalData, + BooleanSelectionRuleNodalData, + CutoffSelectionRuleElementalData, + CutoffSelectionRuleNodalData, + CylindricalSelectionRuleElementalData, + CylindricalSelectionRuleNodalData, + ElementSetElementalData, + ElementSetNodalData, + GeometricalSelectionRuleElementalData, + GeometricalSelectionRuleNodalData, + ImportedSolidModelElementalData, + ImportedSolidModelNodalData, + MeshData, + ModelElementalData, + ModelingPlyElementalData, + ModelingPlyNodalData, + ModelNodalData, + OrientedSelectionSetElementalData, + OrientedSelectionSetNodalData, + ParallelSelectionRuleElementalData, + ParallelSelectionRuleNodalData, + ProductionPlyElementalData, + ProductionPlyNodalData, + ScalarData, + SolidElementSetElementalData, + SolidElementSetNodalData, + SolidModelElementalData, + SolidModelNodalData, + SphericalSelectionRuleElementalData, + SphericalSelectionRuleNodalData, + TriangleMesh, + TubeSelectionRuleElementalData, + TubeSelectionRuleNodalData, + VariableOffsetSelectionRuleElementalData, + VariableOffsetSelectionRuleNodalData, + VectorData, +) + +__all__ = [ + "AnalysisPlyElementalData", + "AnalysisPlyNodalData", + "BooleanSelectionRuleElementalData", + "BooleanSelectionRuleNodalData", + "CutoffSelectionRuleElementalData", + "CutoffSelectionRuleNodalData", + "CylindricalSelectionRuleElementalData", + "CylindricalSelectionRuleNodalData", + "ElementSetElementalData", + "ElementSetNodalData", + "GeometricalSelectionRuleElementalData", + "GeometricalSelectionRuleNodalData", + "ImportedSolidModelElementalData", + "ImportedSolidModelNodalData", + "MeshData", + "ModelElementalData", + "ModelingPlyElementalData", + "ModelingPlyNodalData", + "ModelNodalData", + "OrientedSelectionSetElementalData", + "OrientedSelectionSetNodalData", + "ParallelSelectionRuleElementalData", + "ParallelSelectionRuleNodalData", + "ProductionPlyElementalData", + "ProductionPlyNodalData", + "ScalarData", + "SolidElementSetElementalData", + "SolidElementSetNodalData", + "SolidModelElementalData", + "SolidModelNodalData", + "SphericalSelectionRuleElementalData", + "SphericalSelectionRuleNodalData", + "TriangleMesh", + "TubeSelectionRuleElementalData", + "TubeSelectionRuleNodalData", + "VariableOffsetSelectionRuleElementalData", + "VariableOffsetSelectionRuleNodalData", + "VectorData", +] diff --git a/tests/unittests/test_analysis_ply.py b/tests/unittests/test_analysis_ply.py index 48bd919c76..a3c56d2a00 100644 --- a/tests/unittests/test_analysis_ply.py +++ b/tests/unittests/test_analysis_ply.py @@ -23,7 +23,8 @@ from numpy.testing import assert_equal import pytest -from ansys.acp.core import AnalysisPlyElementalData, AnalysisPlyNodalData, FabricWithAngle, Model +from ansys.acp.core import FabricWithAngle, Model +from ansys.acp.core.mesh_data import AnalysisPlyElementalData, AnalysisPlyNodalData from .common.tree_object_tester import TreeObjectTesterReadOnly diff --git a/tests/unittests/test_boolean_selection_rule.py b/tests/unittests/test_boolean_selection_rule.py index db49b6df3f..cfd1eb532e 100644 --- a/tests/unittests/test_boolean_selection_rule.py +++ b/tests/unittests/test_boolean_selection_rule.py @@ -22,11 +22,10 @@ import pytest -from ansys.acp.core import ( - BooleanOperationType, +from ansys.acp.core import BooleanOperationType, LinkedSelectionRule +from ansys.acp.core.mesh_data import ( BooleanSelectionRuleElementalData, BooleanSelectionRuleNodalData, - LinkedSelectionRule, ) from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester diff --git a/tests/unittests/test_cutoff_selection_rule.py b/tests/unittests/test_cutoff_selection_rule.py index 79ccdeeb5d..2157117777 100644 --- a/tests/unittests/test_cutoff_selection_rule.py +++ b/tests/unittests/test_cutoff_selection_rule.py @@ -22,12 +22,8 @@ import pytest -from ansys.acp.core import ( - CutoffRuleType, - CutoffSelectionRuleElementalData, - CutoffSelectionRuleNodalData, - PlyCutoffType, -) +from ansys.acp.core import CutoffRuleType, PlyCutoffType +from ansys.acp.core.mesh_data import CutoffSelectionRuleElementalData, CutoffSelectionRuleNodalData from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester diff --git a/tests/unittests/test_cylindrical_selection_rule.py b/tests/unittests/test_cylindrical_selection_rule.py index c3c55a13cc..867a2a776a 100644 --- a/tests/unittests/test_cylindrical_selection_rule.py +++ b/tests/unittests/test_cylindrical_selection_rule.py @@ -22,7 +22,10 @@ import pytest -from ansys.acp.core import CylindricalSelectionRuleElementalData, CylindricalSelectionRuleNodalData +from ansys.acp.core.mesh_data import ( + CylindricalSelectionRuleElementalData, + CylindricalSelectionRuleNodalData, +) from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester diff --git a/tests/unittests/test_geometrical_selection_rule.py b/tests/unittests/test_geometrical_selection_rule.py index 3a924dc867..1759b5a02c 100644 --- a/tests/unittests/test_geometrical_selection_rule.py +++ b/tests/unittests/test_geometrical_selection_rule.py @@ -22,8 +22,8 @@ import pytest -from ansys.acp.core import ( - GeometricalRuleType, +from ansys.acp.core import GeometricalRuleType +from ansys.acp.core.mesh_data import ( GeometricalSelectionRuleElementalData, GeometricalSelectionRuleNodalData, ) diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 97dd94750b..b8937f1c86 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -31,7 +31,8 @@ import pytest import pyvista -from ansys.acp.core import ElementalDataType, UnitSystemType, VectorData +from ansys.acp.core import ElementalDataType, UnitSystemType +from ansys.acp.core.mesh_data import VectorData from .helpers import check_property diff --git a/tests/unittests/test_modeling_ply.py b/tests/unittests/test_modeling_ply.py index 7697995646..642cd81365 100644 --- a/tests/unittests/test_modeling_ply.py +++ b/tests/unittests/test_modeling_ply.py @@ -38,8 +38,8 @@ TaperEdge, ThicknessFieldType, ThicknessType, - VectorData, ) +from ansys.acp.core.mesh_data import VectorData from .common.linked_object_list_tester import LinkedObjectListTestCase, LinkedObjectListTester from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester diff --git a/tests/unittests/test_parallel_selection_rule.py b/tests/unittests/test_parallel_selection_rule.py index 62bd32da35..2b3b327edc 100644 --- a/tests/unittests/test_parallel_selection_rule.py +++ b/tests/unittests/test_parallel_selection_rule.py @@ -22,8 +22,8 @@ import pytest -from ansys.acp.core import ( - LinkedSelectionRule, +from ansys.acp.core import LinkedSelectionRule +from ansys.acp.core.mesh_data import ( ParallelSelectionRuleElementalData, ParallelSelectionRuleNodalData, ) diff --git a/tests/unittests/test_production_ply.py b/tests/unittests/test_production_ply.py index ef3b174da9..1104ee7eb1 100644 --- a/tests/unittests/test_production_ply.py +++ b/tests/unittests/test_production_ply.py @@ -22,7 +22,8 @@ import pytest -from ansys.acp.core import Model, ModelingPly, ProductionPlyElementalData, ProductionPlyNodalData +from ansys.acp.core import Model, ModelingPly +from ansys.acp.core.mesh_data import ProductionPlyElementalData, ProductionPlyNodalData from .common.tree_object_tester import TreeObjectTesterReadOnly diff --git a/tests/unittests/test_spherical_selection_rule.py b/tests/unittests/test_spherical_selection_rule.py index fa71608fac..dc7043b7e7 100644 --- a/tests/unittests/test_spherical_selection_rule.py +++ b/tests/unittests/test_spherical_selection_rule.py @@ -22,7 +22,10 @@ import pytest -from ansys.acp.core import SphericalSelectionRuleElementalData, SphericalSelectionRuleNodalData +from ansys.acp.core.mesh_data import ( + SphericalSelectionRuleElementalData, + SphericalSelectionRuleNodalData, +) from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester diff --git a/tests/unittests/test_tube_selection_rule.py b/tests/unittests/test_tube_selection_rule.py index c18a7b20ab..0b08a7ad5c 100644 --- a/tests/unittests/test_tube_selection_rule.py +++ b/tests/unittests/test_tube_selection_rule.py @@ -22,7 +22,7 @@ import pytest -from ansys.acp.core import TubeSelectionRuleElementalData, TubeSelectionRuleNodalData +from ansys.acp.core.mesh_data import TubeSelectionRuleElementalData, TubeSelectionRuleNodalData from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester diff --git a/tests/unittests/test_variable_offset_selection_rule.py b/tests/unittests/test_variable_offset_selection_rule.py index 7678ccb910..c25f8f9d83 100644 --- a/tests/unittests/test_variable_offset_selection_rule.py +++ b/tests/unittests/test_variable_offset_selection_rule.py @@ -22,7 +22,7 @@ import pytest -from ansys.acp.core import ( +from ansys.acp.core.mesh_data import ( VariableOffsetSelectionRuleElementalData, VariableOffsetSelectionRuleNodalData, ) diff --git a/type_checks/plots.py b/type_checks/plots.py index 85c901f85e..721c9fe710 100644 --- a/type_checks/plots.py +++ b/type_checks/plots.py @@ -3,8 +3,8 @@ import numpy as np from typing_extensions import assert_type -from ansys.acp.core import Model, ScalarData, VectorData -from ansys.acp.core._tree_objects.modeling_ply import ModelingPlyElementalData +from ansys.acp.core import Model +from ansys.acp.core.mesh_data import ModelingPlyElementalData, ScalarData, VectorData model = Model() From fa58a450d5579accbf5b76c7c5d279eca88c75b5 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 21 Nov 2024 10:31:22 +0100 Subject: [PATCH 67/96] Fix links at the end of the getting started page (#688) The links were using explicit (relative) paths, which were outdated. This PR changes the links to use the `:ref:` directive instead, which is more robust. --- doc/source/api/index.rst | 44 ++++++++++++++---------- doc/source/conf.py | 9 +++-- doc/source/contributing.rst | 2 ++ doc/source/index.rst | 44 +++++++++++------------- doc/source/intro.rst | 8 +++-- doc/source/user_guide/concepts/index.rst | 2 ++ doc/source/user_guide/howto/index.rst | 2 ++ 7 files changed, 65 insertions(+), 46 deletions(-) diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index df21e6b281..1c86254675 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -1,24 +1,32 @@ +.. _api_reference: + API reference ============= -This section describes the API of the public PyACP classes, functions, -and attributes. +.. jinja:: conditional_skip + + {% if not skip_api %} + This section describes the API of the public PyACP classes, functions, + and attributes. -.. currentmodule:: ansys.acp.core + .. currentmodule:: ansys.acp.core -.. toctree:: - :maxdepth: 2 + .. toctree:: + :maxdepth: 2 - server - tree_objects - mesh_data - linked_object_definitions - material_property_sets - enum_types - other_types - plot_utils - other_utils - workflow - example_helpers - mechanical_integration_helpers - internal + server + tree_objects + mesh_data + linked_object_definitions + material_property_sets + enum_types + other_types + plot_utils + other_utils + workflow + example_helpers + mechanical_integration_helpers + internal + {% else %} + The API reference is not available in this documentation build. + {% endif %} diff --git a/doc/source/conf.py b/doc/source/conf.py index 487f1da097..9d50aa15d6 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -110,9 +110,14 @@ def _signature( SKIP_GALLERY = os.environ.get("PYACP_DOC_SKIP_GALLERY", "0").lower() in ("1", "true") SKIP_API = os.environ.get("PYACP_DOC_SKIP_API", "0").lower() in ("1", "true") -exclude_patterns = [] +# nested example index files are directly included in the parent index file +exclude_patterns = ["examples/*/index.rst"] if SKIP_API: - exclude_patterns.append("api/*") + # Exclude all API documentation except the index + # The 'api/!(index).rst' syntax does not appear to be supported by Sphinx + for file_path in pathlib.Path("api").glob("**/*.rst"): + if str(file_path) != "api/index.rst": + exclude_patterns.append(str(file_path)) jinja_contexts = { diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index 5a8a2ce24d..eebfc14130 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -1,3 +1,5 @@ +.. _contributing: + Contribute ========== diff --git a/doc/source/index.rst b/doc/source/index.rst index 8420d7f399..d7ec9df3fb 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,16 +1,15 @@ -.. jinja:: conditional_skip - .. toctree:: - :hidden: - :maxdepth: 3 - intro - user_guide/index - examples/index - {% if not skip_api %} - api/index - {% endif %} - contributing +.. toctree:: + :hidden: + :maxdepth: 3 + + PyACP home + intro + user_guide/index + examples/index + api/index + contributing PyACP @@ -35,40 +34,39 @@ optimization of composite structures. :gutter: 2 .. grid-item-card:: :octicon:`rocket` Getting started - :link: intro - :link-type: doc + :link: getting_started + :link-type: ref Contains installation instructions and a simple example to get you started with PyACP. .. grid-item-card:: :octicon:`tools` How-to guides - :link: user_guide/howto/index - :link-type: doc + :link: howto + :link-type: ref Guides on how to achieve specific tasks with PyACP. .. grid-item-card:: :octicon:`light-bulb` Concepts - :link: user_guide/concepts/index - :link-type: doc + :link: concepts + :link-type: ref Explains the concepts and terminology used in PyACP. .. grid-item-card:: :octicon:`play` Examples - :link: examples/index - :link-type: doc + :link: ref_examples + :link-type: ref A collection of examples demonstrating the capabilities of PyACP. .. grid-item-card:: :octicon:`file-code` API reference - {% if not skip_api %}:link: api/index - :link-type: doc - {% endif %} + :link: api_reference + :link-type: ref Describes the public Python classes, methods, and functions. .. grid-item-card:: :octicon:`code` Contributing :link: contributing - :link-type: doc + :link-type: ref Information on how to contribute to PyACP. diff --git a/doc/source/intro.rst b/doc/source/intro.rst index 64b29c95cf..291d205d52 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -1,3 +1,5 @@ +.. _getting_started: + Getting started --------------- @@ -153,9 +155,9 @@ Continue exploring This is just a brief introduction to PyACP. To learn more: -- Check out the `examples `_ to see complete examples of how to use PyACP. -- The `how-to guides `_ provide instructions on how to perform specific tasks. -- The `API reference `_ provides detailed information on all available classes and methods. +- Check out the :ref:`examples ` to see complete examples of how to use PyACP. +- The :ref:`how-to guides ` provide instructions on how to perform specific tasks. +- The :ref:`API reference ` provides detailed information on all available classes and methods. .. testcode:: :hide: diff --git a/doc/source/user_guide/concepts/index.rst b/doc/source/user_guide/concepts/index.rst index 3b96c1a561..7f4f4d87e6 100644 --- a/doc/source/user_guide/concepts/index.rst +++ b/doc/source/user_guide/concepts/index.rst @@ -1,3 +1,5 @@ +.. _concepts: + Concepts -------- diff --git a/doc/source/user_guide/howto/index.rst b/doc/source/user_guide/howto/index.rst index 3b00320ff9..01e827b639 100644 --- a/doc/source/user_guide/howto/index.rst +++ b/doc/source/user_guide/howto/index.rst @@ -1,3 +1,5 @@ +.. _howto: + How-to guides ------------- From b77057a2a9d140fd423396ca4dd9a1c9693dfdda Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 21 Nov 2024 14:55:59 +0100 Subject: [PATCH 68/96] Make PyVista optional, fix optional deps definition (#685) Make the `pyvista` dependency optional, adding the `plotting` extra dependency group to install it. Add a ``requires_pyvista`` decorator, which is added to functions which import PyVista internally. The user is advised to run ``pip install ansys-acp-core[plotting]`` to install PyVista if it is missing. Mark tests which require PyVista with ``pytest.mark.plotting``, and add a separate set of CI tests which: - checks that PyACP can be imported if _only_ the `main` dependency group is installed - runs the tests with only the `main` and `test` dependency groups, with `-m "not plotting"` to exclude plot tests Remove the duplication of dependencies between the `extras` and the `dev` dependency, since that didn't work properly. Instead add ``--all-extras`` or ``--extras `` to ``poetry install`` where needed. Move some internal utilities from the top-level ``ansys.acp.core`` to the ``ansys.acp.core._utils`` module. --- .github/workflows/ci_cd.yml | 94 +- README.rst | 4 +- doc/source/conf.py | 6 +- doc/source/intro.rst | 2 +- poetry.lock | 1193 ++++++++--------- pyproject.toml | 31 +- src/ansys/acp/core/_model_printer.py | 4 +- src/ansys/acp/core/_plotter.py | 13 +- src/ansys/acp/core/_recursive_copy.py | 2 +- src/ansys/acp/core/_server/acp_instance.py | 2 +- src/ansys/acp/core/_server/common.py | 2 +- src/ansys/acp/core/_server/docker_compose.py | 3 - .../_tree_objects/_elemental_or_nodal_data.py | 6 +- .../_grpc_helpers/enum_wrapper.py | 2 +- .../acp/core/_tree_objects/_mesh_data.py | 12 +- .../core/_tree_objects/_solid_model_export.py | 2 +- .../acp/core/_tree_objects/cad_geometry.py | 16 +- .../_tree_objects/imported_solid_model.py | 2 +- src/ansys/acp/core/_tree_objects/model.py | 2 +- src/ansys/acp/core/_utils/path_to_str.py | 2 +- .../acp/core/_utils/pyvista_import_check.py | 49 + .../acp/core/_utils/string_manipulation.py | 26 + .../typing_helper.py} | 0 src/ansys/acp/core/_utils/visualization.py | 4 - src/ansys/acp/core/_workflow.py | 2 +- .../core/mechanical_integration_helpers.py | 2 +- tests/conftest.py | 2 +- tests/unittests/test_edge_property_list.py | 2 +- tests/unittests/test_model.py | 10 +- tests/unittests/test_modeling_ply.py | 13 +- tests/unittests/test_plot_utils.py | 6 +- 31 files changed, 843 insertions(+), 673 deletions(-) create mode 100644 src/ansys/acp/core/_utils/pyvista_import_check.py create mode 100644 src/ansys/acp/core/_utils/string_manipulation.py rename src/ansys/acp/core/{_typing_helper.py => _utils/typing_helper.py} (100%) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index ee75a4659f..0d3faf178c 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -55,7 +55,7 @@ jobs: run: | pip install -U pip pip install 'poetry!=1.7.0' - poetry install --with dev,test + poetry install --with dev,test --all-extras - name: Build API package from custom branch if: "${{ env.API_BRANCH != '' }}" @@ -116,6 +116,90 @@ jobs: token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} python-package-name: 'ansys-acp-core' dev-mode: ${{ github.ref != 'refs/heads/main' }} + # In principle the 'plotting' extra would be sufficient, but poetry + # then removes 'setuptools' which is needed by the action. + extra-targets: "all" + + testing-minimum-deps: + name: Testing with minimum dependencies + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + server-version: ["latest"] + steps: + - uses: actions/checkout@v4 + + - name: Pip cache + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: pip-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}-testing-minimum-deps + restore-keys: | + pip-${{ runner.os }}-${{ matrix.python-version }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: | + 3.10 + ${{ matrix.python-version }} + + - name: Install library, with only the 'main' group + run: | + pip install -U pip + pip install 'poetry!=1.7.0' + poetry install --only main + + - name: Check that PyACP can be imported + run: | + poetry run python -c "import ansys.acp.core" + + - name: Install the 'test' group + run: | + poetry install --with test + + - name: Build API package from custom branch + if: "${{ env.API_BRANCH != '' }}" + run: | + python3.10 -m venv .api_builder_venv + . .api_builder_venv/bin/activate + python -m pip install --upgrade pip wheel + mkdir .api_package + python -m pip wheel --no-deps --wheel-dir .api_package git+https://github.com/ansys/ansys-api-acp.git@${{ env.API_BRANCH }} + + - name: Install custom API branch package + if: "${{ env.API_BRANCH != '' }}" + # The --no-deps flag is added since this may cause dependency conflicts with + # other transitive dependencies. For example, when a newer version of protobuf + # is installed. + run: | + poetry run pip install --no-deps --force-reinstall .api_package/*.whl + + - name: Login in Github Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Unit testing + working-directory: tests/unittests + run: | + docker pull $IMAGE_NAME + poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html -m "not plotting" + env: + LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }} + IMAGE_NAME: ghcr.io/ansys/acp:${{ matrix.server-version }} + + - name: "Upload coverage to Codecov" + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: coverage.xml + flags: 'server-${{ matrix.server-version }},python-${{ matrix.python-version }},minimum-deps' testing: name: Testing @@ -155,7 +239,7 @@ jobs: run: | pip install -U pip pip install 'poetry!=1.7.0' - poetry install --with test + poetry install --with test --extras plotting - name: Build API package from custom branch if: "${{ env.API_BRANCH != '' }}" @@ -242,7 +326,7 @@ jobs: run: | pip install -U pip pip install 'poetry!=1.7.0' - poetry install --with test,dev + poetry install --with test,dev --all-extras - name: Build API package from custom branch if: "${{ env.API_BRANCH != '' }}" @@ -338,7 +422,7 @@ jobs: run: | pip install -U pip pip install 'poetry!=1.7.0' - poetry install --with dev + poetry install --with dev --all-extras - name: Build API package from custom branch if: "${{ env.API_BRANCH != '' }}" @@ -431,7 +515,7 @@ jobs: build: name: Build library runs-on: ubuntu-latest - needs: [code-style, testing, doc-style, docs, build-wheelhouse, doctest] # TODO: add check-vulnerabilities once we know it works on main + needs: [code-style, testing, testing-minimum-deps, doc-style, docs, build-wheelhouse, doctest] # TODO: add check-vulnerabilities once we know it works on main timeout-minutes: 30 steps: - name: Build library source and wheel artifacts diff --git a/README.rst b/README.rst index 61b93afbd0..0753637ba8 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,7 @@ Install PyACP with: .. code-block:: - pip install ansys-acp-core + pip install ansys-acp-core[all] For installing PyACP in development mode, see the `Development Setup`_ instructions below. @@ -120,7 +120,7 @@ You will need to follow these steps: .. code-block:: bash - poetry install --with dev,test + poetry install --with dev,test --all-extras This step installs PyACP in an editable mode (no build step is needed, no re-install when changing the code). diff --git a/doc/source/conf.py b/doc/source/conf.py index 9d50aa15d6..a93f1bbdfa 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -33,6 +33,8 @@ def _signature( from collections.abc import Sequence # noqa: F401 import numpy as np # noqa: F401 + import pyvista # noqa: F401 + from pyvista.core.pointset import PolyData, UnstructuredGrid # noqa: F401 from ansys.acp.core import ( # noqa: F401 BooleanSelectionRule, @@ -54,7 +56,7 @@ def _signature( ) from ansys.acp.core._tree_objects.sensor import _LINKABLE_ENTITY_TYPES # noqa: F401 from ansys.acp.core._tree_objects.sublaminate import _LINKABLE_MATERIAL_TYPES # noqa: F401 - from ansys.acp.core._typing_helper import StrEnum + from ansys.acp.core._utils.typing_helper import StrEnum from ansys.acp.core.mesh_data import MeshData, ScalarData, VectorData # noqa: F401 from ansys.dpf.composites.data_sources import ContinuousFiberCompositesFiles # noqa: F401 from ansys.dpf.core import UnitSystem # noqa: F401 @@ -316,7 +318,7 @@ def _signature( templates_path = ["_templates"] # The suffix(es) of source filenames. -source_suffix = ".rst" +source_suffix = {".rst": "restructuredtext"} # The master toctree document. master_doc = "index" diff --git a/doc/source/intro.rst b/doc/source/intro.rst index 291d205d52..d69fef97d8 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -10,7 +10,7 @@ PyACP supports Ansys 2024 R2 and later. To install PyACP, run the following comm .. code-block:: bash - pip install ansys-acp-core[examples] + pip install ansys-acp-core[all] You should use a `virtual environment `_, because it keeps Python packages isolated from your system Python. diff --git a/poetry.lock b/poetry.lock index 9f72df6eef..7bc6db67ee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -22,7 +22,7 @@ tests = ["hypothesis", "pytest"] name = "aiohappyeyeballs" version = "2.4.3" description = "Happy Eyeballs for asyncio" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, @@ -31,112 +31,98 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.10" +version = "3.11.6" description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" +optional = true +python-versions = ">=3.9" files = [ - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68"}, - {file = "aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257"}, - {file = "aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a"}, - {file = "aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94"}, - {file = "aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205"}, - {file = "aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628"}, - {file = "aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b"}, - {file = "aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8"}, - {file = "aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b"}, - {file = "aiohttp-3.10.10-cp38-cp38-win32.whl", hash = "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c"}, - {file = "aiohttp-3.10.10-cp38-cp38-win_amd64.whl", hash = "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91"}, - {file = "aiohttp-3.10.10-cp39-cp39-win32.whl", hash = "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983"}, - {file = "aiohttp-3.10.10-cp39-cp39-win_amd64.whl", hash = "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23"}, - {file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"}, + {file = "aiohttp-3.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7510b3ca2275691875ddf072a5b6cd129278d11fe09301add7d292fc8d3432de"}, + {file = "aiohttp-3.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfab0d2c3380c588fc925168533edb21d3448ad76c3eadc360ff963019161724"}, + {file = "aiohttp-3.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf02dba0f342f3a8228f43fae256aafc21c4bc85bffcf537ce4582e2b1565188"}, + {file = "aiohttp-3.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92daedf7221392e7a7984915ca1b0481a94c71457c2f82548414a41d65555e70"}, + {file = "aiohttp-3.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2274a7876e03429e3218589a6d3611a194bdce08c3f1e19962e23370b47c0313"}, + {file = "aiohttp-3.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a2e1eae2d2f62f3660a1591e16e543b2498358593a73b193006fb89ee37abc6"}, + {file = "aiohttp-3.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:978ec3fb0a42efcd98aae608f58c6cfcececaf0a50b4e86ee3ea0d0a574ab73b"}, + {file = "aiohttp-3.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51f87b27d9219ed4e202ed8d6f1bb96f829e5eeff18db0d52f592af6de6bdbf"}, + {file = "aiohttp-3.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:04d1a02a669d26e833c8099992c17f557e3b2fdb7960a0c455d7b1cbcb05121d"}, + {file = "aiohttp-3.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3679d5fcbc7f1ab518ab4993f12f80afb63933f6afb21b9b272793d398303b98"}, + {file = "aiohttp-3.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a4b24e03d04893b5c8ec9cd5f2f11dc9c8695c4e2416d2ac2ce6c782e4e5ffa5"}, + {file = "aiohttp-3.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d9abdfd35ecff1c95f270b7606819a0e2de9e06fa86b15d9080de26594cf4c23"}, + {file = "aiohttp-3.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b5c3e7928a0ad80887a5eba1c1da1830512ddfe7394d805badda45c03db3109"}, + {file = "aiohttp-3.11.6-cp310-cp310-win32.whl", hash = "sha256:913dd9e9378f3c38aeb5c4fb2b8383d6490bc43f3b427ae79f2870651ae08f22"}, + {file = "aiohttp-3.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:4ac26d482c2000c3a59bf757a77adc972828c9d4177b4bd432a46ba682ca7271"}, + {file = "aiohttp-3.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26ac4c960ea8debf557357a172b3ef201f2236a462aefa1bc17683a75483e518"}, + {file = "aiohttp-3.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b1f13ebc99fb98c7c13057b748f05224ccc36d17dee18136c695ef23faaf4ff"}, + {file = "aiohttp-3.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4679f1a47516189fab1774f7e45a6c7cac916224c91f5f94676f18d0b64ab134"}, + {file = "aiohttp-3.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74491fdb3d140ff561ea2128cb7af9ba0a360067ee91074af899c9614f88a18f"}, + {file = "aiohttp-3.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f51e1a90412d387e62aa2d243998c5eddb71373b199d811e6ed862a9f34f9758"}, + {file = "aiohttp-3.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72ab89510511c3bb703d0bb5504787b11e0ed8be928ed2a7cf1cda9280628430"}, + {file = "aiohttp-3.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6681c9e046d99646e8059266688374a063da85b2e4c0ebfa078cda414905d080"}, + {file = "aiohttp-3.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a17f8a6d3ab72cbbd137e494d1a23fbd3ea973db39587941f32901bb3c5c350"}, + {file = "aiohttp-3.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:867affc7612a314b95f74d93aac550ce0909bc6f0b6c658cc856890f4d326542"}, + {file = "aiohttp-3.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:00d894ebd609d5a423acef885bd61e7f6a972153f99c5b3ea45fc01fe909196c"}, + {file = "aiohttp-3.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:614c87be9d0d64477d1e4b663bdc5d1534fc0a7ebd23fb08347ab9fd5fe20fd7"}, + {file = "aiohttp-3.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:533ed46cf772f28f3bffae81c0573d916a64dee590b5dfaa3f3d11491da05b95"}, + {file = "aiohttp-3.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589884cfbc09813afb1454816b45677e983442e146183143f988f7f5a040791a"}, + {file = "aiohttp-3.11.6-cp311-cp311-win32.whl", hash = "sha256:1da63633ba921669eec3d7e080459d4ceb663752b3dafb2f31f18edd248d2170"}, + {file = "aiohttp-3.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:d778ddda09622e7d83095cc8051698a0084c155a1474bfee9bac27d8613dbc31"}, + {file = "aiohttp-3.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:943a952df105a5305257984e7a1f5c2d0fd8564ff33647693c4d07eb2315446d"}, + {file = "aiohttp-3.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d24ec28b7658970a1f1d98608d67f88376c7e503d9d45ff2ba1949c09f2b358c"}, + {file = "aiohttp-3.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6720e809a660fdb9bec7c168c582e11cfedce339af0a5ca847a5d5b588dce826"}, + {file = "aiohttp-3.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4252d30da0ada6e6841b325869c7ef5104b488e8dd57ec439892abbb8d7b3615"}, + {file = "aiohttp-3.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f65f43ff01b238aa0b5c47962c83830a49577efe31bd37c1400c3d11d8a32835"}, + {file = "aiohttp-3.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc5933f6c9b26404444d36babb650664f984b8e5fa0694540e7b7315d11a4ff"}, + {file = "aiohttp-3.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bf546ba0c029dfffc718c4b67748687fd4f341b07b7c8f1719d6a3a46164798"}, + {file = "aiohttp-3.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c351d05bbeae30c088009c0bb3b17dda04fd854f91cc6196c448349cc98f71c3"}, + {file = "aiohttp-3.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:10499079b063576fad1597898de3f9c0a2ce617c19cc7cd6b62fdcff6b408bf7"}, + {file = "aiohttp-3.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:442ee82eda47dd59798d6866ce020fb8d02ea31ac9ac82b3d719ed349e6a9d52"}, + {file = "aiohttp-3.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:86fce9127bc317119b34786d9e9ae8af4508a103158828a535f56d201da6ab19"}, + {file = "aiohttp-3.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:973d26a5537ce5d050302eb3cd876457451745b1da0624cbb483217970e12567"}, + {file = "aiohttp-3.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:532b8f038a4e001137d3600cea5d3439d1881df41bdf44d0f9651264d562fdf0"}, + {file = "aiohttp-3.11.6-cp312-cp312-win32.whl", hash = "sha256:4863c59f748dbe147da82b389931f2a676aebc9d3419813ed5ca32d057c9cb32"}, + {file = "aiohttp-3.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:5d7f481f82c18ac1f7986e31ba6eea9be8b2e2c86f1ef035b6866179b6c5dd68"}, + {file = "aiohttp-3.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:40f502350496ba4c6820816d3164f8a0297b9aa4e95d910da31beb189866a9df"}, + {file = "aiohttp-3.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9072669b0bffb40f1f6977d0b5e8a296edc964f9cefca3a18e68649c214d0ce3"}, + {file = "aiohttp-3.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:518160ecf4e6ffd61715bc9173da0925fcce44ae6c7ca3d3f098fe42585370fb"}, + {file = "aiohttp-3.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69cc1b45115ac44795b63529aa5caa9674be057f11271f65474127b24fc1ce6"}, + {file = "aiohttp-3.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6be90a6beced41653bda34afc891617c6d9e8276eef9c183f029f851f0a3c3d"}, + {file = "aiohttp-3.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00c22fe2486308770d22ef86242101d7b0f1e1093ce178f2358f860e5149a551"}, + {file = "aiohttp-3.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2607ebb783e3aeefa017ec8f34b506a727e6b6ab2c4b037d65f0bc7151f4430a"}, + {file = "aiohttp-3.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f761d6819870c2a8537f75f3e2fc610b163150cefa01f9f623945840f601b2c"}, + {file = "aiohttp-3.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e44d1bc6c88f5234115011842219ba27698a5f2deee245c963b180080572aaa2"}, + {file = "aiohttp-3.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e0cb6a1b1f499cb2aa0bab1c9f2169ad6913c735b7447e058e0c29c9e51c0b5"}, + {file = "aiohttp-3.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a76b4d4ca34254dca066acff2120811e2a8183997c135fcafa558280f2cc53f3"}, + {file = "aiohttp-3.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69051c1e45fb18c0ae4d39a075532ff0b015982e7997f19eb5932eb4a3e05c17"}, + {file = "aiohttp-3.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aff2ed18274c0bfe0c1d772781c87d5ca97ae50f439729007cec9644ee9b15fe"}, + {file = "aiohttp-3.11.6-cp313-cp313-win32.whl", hash = "sha256:2fbea25f2d44df809a46414a8baafa5f179d9dda7e60717f07bded56300589b3"}, + {file = "aiohttp-3.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:f77bc29a465c0f9f6573d1abe656d385fa673e34efe615bd4acc50899280ee47"}, + {file = "aiohttp-3.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:de6123b298d17bca9e53581f50a275b36e10d98e8137eb743ce69ee766dbdfe9"}, + {file = "aiohttp-3.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a10200f705f4fff00e148b7f41e5d1d929c7cd4ac523c659171a0ea8284cd6fb"}, + {file = "aiohttp-3.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7776ef6901b54dd557128d96c71e412eec0c39ebc07567e405ac98737995aad"}, + {file = "aiohttp-3.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e5c2a55583cd91936baf73d223807bb93ace6eb1fe54424782690f2707162ab"}, + {file = "aiohttp-3.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b032bd6cf7422583bf44f233f4a1489fee53c6d35920123a208adc54e2aba41e"}, + {file = "aiohttp-3.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fe2d99acbc5cf606f75d7347bf3a027c24c27bc052d470fb156f4cfcea5739"}, + {file = "aiohttp-3.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84a79c366375c2250934d1238abe5d5ea7754c823a1c7df0c52bf0a2bfded6a9"}, + {file = "aiohttp-3.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c33cbbe97dc94a34d1295a7bb68f82727bcbff2b284f73ae7e58ecc05903da97"}, + {file = "aiohttp-3.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:19e4fb9ac727834b003338dcdd27dcfe0de4fb44082b01b34ed0ab67c3469fc9"}, + {file = "aiohttp-3.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a97f6b2afbe1d27220c0c14ea978e09fb4868f462ef3d56d810d206bd2e057a2"}, + {file = "aiohttp-3.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c3f7afeea03a9bc49be6053dfd30809cd442cc12627d6ca08babd1c1f9e04ccf"}, + {file = "aiohttp-3.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0d10967600ce5bb69ddcb3e18d84b278efb5199d8b24c3c71a4959c2f08acfd0"}, + {file = "aiohttp-3.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:60f2f631b9fe7aa321fa0f0ff3f5d8b9f7f9b72afd4eecef61c33cf1cfea5d58"}, + {file = "aiohttp-3.11.6-cp39-cp39-win32.whl", hash = "sha256:4d2b75333deb5c5f61bac5a48bba3dbc142eebbd3947d98788b6ef9cc48628ae"}, + {file = "aiohttp-3.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:8908c235421972a2e02abcef87d16084aabfe825d14cc9a1debd609b3cfffbea"}, + {file = "aiohttp-3.11.6.tar.gz", hash = "sha256:fd9f55c1b51ae1c20a1afe7216a64a88d38afee063baa23c7fce03757023c999"}, ] [package.dependencies] aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.12.0,<2.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] @@ -145,7 +131,7 @@ speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, @@ -185,7 +171,7 @@ protobuf = ">=3.19,<6" name = "ansys-api-mapdl" version = "0.5.2" description = "Autogenerated python gRPC interface package for ansys-api-mapdl, built on 10:27:13 on 09 July 2024" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "ansys-api-mapdl-0.5.2.tar.gz", hash = "sha256:0d1f043884ba052b3afcbcd4e359a2baa81d017cc386b857607c220c12d32da5"}, @@ -200,7 +186,7 @@ protobuf = ">=3.19,<5" name = "ansys-api-mechanical" version = "0.1.2" description = "Autogenerated python gRPC interface package for ansys-api-mechanical, built on 14:21:07 on 30 April 2024" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "ansys_api_mechanical-0.1.2-py3-none-any.whl", hash = "sha256:dfb344e4d32850570f7745061b73fbb7e6f69932e27452e68ee8bfde9c26ce99"}, @@ -215,7 +201,7 @@ protobuf = ">=3.19,<6" name = "ansys-api-platform-instancemanagement" version = "1.1.0" description = "Autogenerated python gRPC interface package for ansys-api-platform-instancemanagement, built on 09:44:35 on 24 April 2024" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "ansys_api_platform_instancemanagement-1.1.0-py3-none-any.whl", hash = "sha256:f7f5f310f600b9218976f363987681ea03f6d4de8958b7d144b3904c76bb8678"}, @@ -228,24 +214,24 @@ protobuf = ">=3.19,<6" [[package]] name = "ansys-api-tools-filetransfer" -version = "0.1.0" +version = "0.1.1" description = "Autogenerated python gRPC interface package for ansys-api-tools-filetransfer." optional = false python-versions = ">=3.7" files = [ - {file = "ansys-api-tools-filetransfer-0.1.0.tar.gz", hash = "sha256:f6e58af43016a0a4e0e7f3569cbface912d0a1004e858db90902c20371dd7345"}, - {file = "ansys_api_tools_filetransfer-0.1.0-py3-none-any.whl", hash = "sha256:0c888e3e2f76db82842d9437bf5521c38578feeb74ab56352702fbcf1cfec934"}, + {file = "ansys_api_tools_filetransfer-0.1.1-py3-none-any.whl", hash = "sha256:84baefa5e82106e74a0a49f01ba99283297d15094d47ea1a0490f7535e2f4a5b"}, + {file = "ansys_api_tools_filetransfer-0.1.1.tar.gz", hash = "sha256:180e41edc767d9ff4ce9980bb5b7002faa04d2a551281e39c01b5b8d5d6d6c92"}, ] [package.dependencies] grpcio = ">=1.17,<2.0" -protobuf = ">=3.19,<5" +protobuf = ">=3.19,<6" [[package]] name = "ansys-dpf-composites" version = "0.6.1" description = "Post-processing of composite structures based on Ansys DPF" -optional = false +optional = true python-versions = "<3.13,>=3.9" files = [ {file = "ansys_dpf_composites-0.6.1-py3-none-any.whl", hash = "sha256:8d78dd9e1974c1b241c57a98c9a25ec138cf8ad5e997c803ab464ce38e118b64"}, @@ -269,7 +255,7 @@ test = ["pytest (>=7.1.2)", "pytest-cov (>=3.0.0)", "pytest-rerunfailures (>=11. name = "ansys-dpf-core" version = "0.13.2" description = "Data Processing Framework - Python Core" -optional = false +optional = true python-versions = "<4,>=3.9" files = [ {file = "ansys_dpf_core-0.13.2-py3-none-any.whl", hash = "sha256:91f6d23d14dbc3485f6141747ba2e3d98212a193b4f681fc587f46497162bb4e"}, @@ -296,7 +282,7 @@ plotting = ["imageio (<2.28.1)", "imageio-ffmpeg", "matplotlib (>=3.2)", "pyvist name = "ansys-mapdl-core" version = "0.68.6" description = "A Python wrapper for Ansys MAPDL." -optional = false +optional = true python-versions = "<3.13,>=3.10" files = [ {file = "ansys_mapdl_core-0.68.6-py3-none-any.whl", hash = "sha256:8a62178e7b0aeb71a40edd4b97cfc2fa7849df643ad574f41b52aebea552b2f8"}, @@ -335,7 +321,7 @@ tests = ["ansys-dpf-core (==0.10.1)", "ansys-tools-visualization-interface (==0. name = "ansys-mapdl-reader" version = "0.54.1" description = "Pythonic interface to files generated by MAPDL" -optional = false +optional = true python-versions = "<4,>=3.7" files = [ {file = "ansys_mapdl_reader-0.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9739a53801c946069f9085a016255a4b1781e4eff50aaec250cadf9ea5af6cd8"}, @@ -372,7 +358,7 @@ vtk = ">=9.0.0" name = "ansys-math-core" version = "0.2.0" description = "A Python wrapper for PyAnsys Math libraries." -optional = false +optional = true python-versions = ">=3.10" files = [ {file = "ansys_math_core-0.2.0-py3-none-any.whl", hash = "sha256:68e554e45d8ec532cb4b5e3c0f902a035a047441654c20d36b0d25c71a11cf70"}, @@ -392,13 +378,13 @@ tests = ["ansys-mapdl-core (==0.68.5)", "numpy (==2.1.2)", "pyansys-tools-report [[package]] name = "ansys-mechanical-core" -version = "0.11.9" +version = "0.11.10" description = "A python wrapper for Ansys Mechanical" -optional = false +optional = true python-versions = "<4.0,>=3.10" files = [ - {file = "ansys_mechanical_core-0.11.9-py3-none-any.whl", hash = "sha256:30f28ff5c5f8abad5a0815eeb2e0e9b330dfad8df238e41d515d72132fb215f7"}, - {file = "ansys_mechanical_core-0.11.9.tar.gz", hash = "sha256:35740cb7e0fcb20ddedf9f84fb5ac7bec22f2bc2fe81f325479737965deb6b66"}, + {file = "ansys_mechanical_core-0.11.10-py3-none-any.whl", hash = "sha256:793595261966207627937d191e822252b0d33939cd2a5ad8e8debbdab0167137"}, + {file = "ansys_mechanical_core-0.11.10.tar.gz", hash = "sha256:6f71f9625b2441816047dd97aefd711edb63591f1434285f091b3f9dcb62b851"}, ] [package.dependencies] @@ -418,15 +404,15 @@ requests = ">=2,<3" tqdm = ">=4.45.0" [package.extras] -doc = ["ansys-sphinx-theme[autoapi] (==1.1.7)", "grpcio (==1.67.0)", "imageio (==2.36.0)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.2)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "panel (==1.5.3)", "plotly (==5.24.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.1.3)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.5.0)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] -tests = ["psutil (==6.1.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-print (==1.0.2)"] +doc = ["ansys-sphinx-theme[autoapi] (==1.2.1)", "grpcio (==1.68.0)", "imageio (==2.36.0)", "imageio-ffmpeg (==0.5.1)", "jupyter_sphinx (==0.5.3)", "jupyterlab (>=3.2.8)", "matplotlib (==3.9.2)", "numpy (==2.1.3)", "numpydoc (==1.8.0)", "pandas (==2.2.3)", "panel (==1.5.4)", "plotly (==5.24.1)", "pypandoc (==1.14)", "pytest-sphinx (==0.6.3)", "pythreejs (==2.4.2)", "pyvista (>=0.39.1)", "sphinx (==8.1.3)", "sphinx-autobuild (==2024.10.3)", "sphinx-autodoc-typehints (==2.5.0)", "sphinx-copybutton (==0.5.2)", "sphinx-gallery (==0.18.0)", "sphinx-notfound-page (==1.0.4)", "sphinx_design (==0.6.1)", "sphinxcontrib-websupport (==2.0.0)", "sphinxemoji (==0.3.1)"] +tests = ["psutil (==6.1.0)", "pytest (==8.3.3)", "pytest-cov (==6.0.0)", "pytest-print (==1.0.2)"] viz = ["ansys-tools-visualization-interface (>=0.2.6)", "usd-core (==24.11)"] [[package]] name = "ansys-mechanical-env" version = "0.1.8" description = "A python wrapper for loading environment variables when using PyMechanical embedded instances in Linux." -optional = false +optional = true python-versions = "<4,>=3.10" files = [ {file = "ansys_mechanical_env-0.1.8-py3-none-any.whl", hash = "sha256:4746c83924cba91137ffd367712a033abf2a8ccc66e7e02b76c1a3a6a80b4bf9"}, @@ -442,7 +428,7 @@ importlib-metadata = ">=4.0" name = "ansys-mechanical-stubs" version = "0.1.4" description = "PyMechanical scripting API stubs." -optional = false +optional = true python-versions = "<4,>=3.10" files = [ {file = "ansys_mechanical_stubs-0.1.4-py3-none-any.whl", hash = "sha256:8c62d88f4c4c10c42044781c46e9dd2c68c3ee9813eff33efdc9328ea4fb8648"}, @@ -458,7 +444,7 @@ tests = ["pytest (==8.3.3)", "pytest-cov (==5.0.0)"] name = "ansys-platform-instancemanagement" version = "1.1.2" description = "A Python wrapper for Ansys platform instancemanagement" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "ansys_platform_instancemanagement-1.1.2-py3-none-any.whl", hash = "sha256:52afe4755c4233985a1f5f33cdb2a163a53db5cee75f04c3a4e47fc81e1d0bb1"}, @@ -477,7 +463,7 @@ tests = ["ansys-api-platform-instancemanagement (==1.0.0)", "grpcio-health-check name = "ansys-pythonnet" version = "3.1.0rc4" description = ".NET and Mono integration for Python (Ansys, Inc. fork)" -optional = false +optional = true python-versions = "<3.13,>=3.7" files = [ {file = "ansys_pythonnet-3.1.0rc4-py3-none-any.whl", hash = "sha256:ff25706acdaec6dec7b2e6b37488fa366945fa992086131916ba826145984a87"}, @@ -566,7 +552,7 @@ tests = ["pyfakefs (==5.7.1)", "pytest (==8.3.3)", "pytest-cov (==6.0.0)"] name = "ansys-tools-visualization-interface" version = "0.5.0" description = "A Python visualization interface for PyAnsys libraries" -optional = false +optional = true python-versions = "<4,>=3.10" files = [ {file = "ansys_tools_visualization_interface-0.5.0-py3-none-any.whl", hash = "sha256:0f73d515e039b4335ab8b902e65fa6d673db809ba07957164adccc08d8906565"}, @@ -588,7 +574,7 @@ tests = ["pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-pyvista (==0.1.9)"] name = "anyio" version = "4.6.2.post1" description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false +optional = true python-versions = ">=3.9" files = [ {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, @@ -632,7 +618,7 @@ files = [ name = "argon2-cffi" version = "23.1.0" description = "Argon2 for Python" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, @@ -652,7 +638,7 @@ typing = ["mypy"] name = "argon2-cffi-bindings" version = "21.2.0" description = "Low-level CFFI bindings for Argon2" -optional = false +optional = true python-versions = ">=3.6" files = [ {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, @@ -689,7 +675,7 @@ tests = ["pytest"] name = "arrow" version = "1.3.0" description = "Better dates & times for Python" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, @@ -724,13 +710,13 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "async-timeout" -version = "4.0.3" +version = "5.0.1" description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" +optional = true +python-versions = ">=3.8" files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] [[package]] @@ -852,7 +838,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.2.0" description = "An easy safelist-based HTML-sanitizing tool." -optional = false +optional = true python-versions = ">=3.9" files = [ {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, @@ -869,7 +855,7 @@ css = ["tinycss2 (>=1.1.0,<1.5)"] name = "cachetools" version = "5.5.0" description = "Extensible memoizing collections and decorators" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, @@ -1109,7 +1095,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "clr-loader" version = "0.2.6" description = "Generic pure Python loader for .NET runtimes" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "clr_loader-0.2.6-py3-none-any.whl", hash = "sha256:79bbfee4bf6ac2f4836d89af2c39e0c32dce5d0c062596185aef380f317507a6"}, @@ -1149,76 +1135,65 @@ test = ["pytest"] [[package]] name = "contourpy" -version = "1.3.0" +version = "1.3.1" description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.9" +optional = true +python-versions = ">=3.10" files = [ - {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, - {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, - {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, - {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, - {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, - {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, - {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, - {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, - {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, - {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, - {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, - {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, - {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, + {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"}, + {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595"}, + {file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697"}, + {file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f"}, + {file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"}, + {file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d"}, + {file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e"}, + {file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1"}, + {file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82"}, + {file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53"}, + {file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699"}, ] [package.dependencies] @@ -1233,73 +1208,73 @@ test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist" [[package]] name = "coverage" -version = "7.6.4" +version = "7.6.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, - {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, - {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, - {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, - {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, - {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, - {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, - {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, - {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, - {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, - {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, - {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, - {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, - {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, - {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, - {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, + {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"}, + {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"}, + {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"}, + {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"}, + {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"}, + {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"}, + {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"}, + {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"}, + {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"}, + {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"}, + {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"}, + {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"}, + {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"}, + {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"}, + {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"}, + {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"}, ] [package.dependencies] @@ -1312,7 +1287,7 @@ toml = ["tomli"] name = "cycler" version = "0.12.1" description = "Composable style cycles" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, @@ -1325,37 +1300,37 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "debugpy" -version = "1.8.7" +version = "1.8.8" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.7-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95fe04a573b8b22896c404365e03f4eda0ce0ba135b7667a1e57bd079793b96b"}, - {file = "debugpy-1.8.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628a11f4b295ffb4141d8242a9bb52b77ad4a63a2ad19217a93be0f77f2c28c9"}, - {file = "debugpy-1.8.7-cp310-cp310-win32.whl", hash = "sha256:85ce9c1d0eebf622f86cc68618ad64bf66c4fc3197d88f74bb695a416837dd55"}, - {file = "debugpy-1.8.7-cp310-cp310-win_amd64.whl", hash = "sha256:29e1571c276d643757ea126d014abda081eb5ea4c851628b33de0c2b6245b037"}, - {file = "debugpy-1.8.7-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:caf528ff9e7308b74a1749c183d6808ffbedbb9fb6af78b033c28974d9b8831f"}, - {file = "debugpy-1.8.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba1d078cf2e1e0b8402e6bda528bf8fda7ccd158c3dba6c012b7897747c41a0"}, - {file = "debugpy-1.8.7-cp311-cp311-win32.whl", hash = "sha256:171899588bcd412151e593bd40d9907133a7622cd6ecdbdb75f89d1551df13c2"}, - {file = "debugpy-1.8.7-cp311-cp311-win_amd64.whl", hash = "sha256:6e1c4ffb0c79f66e89dfd97944f335880f0d50ad29525dc792785384923e2211"}, - {file = "debugpy-1.8.7-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:4d27d842311353ede0ad572600c62e4bcd74f458ee01ab0dd3a1a4457e7e3706"}, - {file = "debugpy-1.8.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c1fd62ae0356e194f3e7b7a92acd931f71fe81c4b3be2c17a7b8a4b546ec2"}, - {file = "debugpy-1.8.7-cp312-cp312-win32.whl", hash = "sha256:2f729228430ef191c1e4df72a75ac94e9bf77413ce5f3f900018712c9da0aaca"}, - {file = "debugpy-1.8.7-cp312-cp312-win_amd64.whl", hash = "sha256:45c30aaefb3e1975e8a0258f5bbd26cd40cde9bfe71e9e5a7ac82e79bad64e39"}, - {file = "debugpy-1.8.7-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:d050a1ec7e925f514f0f6594a1e522580317da31fbda1af71d1530d6ea1f2b40"}, - {file = "debugpy-1.8.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f4349a28e3228a42958f8ddaa6333d6f8282d5edaea456070e48609c5983b7"}, - {file = "debugpy-1.8.7-cp313-cp313-win32.whl", hash = "sha256:11ad72eb9ddb436afb8337891a986302e14944f0f755fd94e90d0d71e9100bba"}, - {file = "debugpy-1.8.7-cp313-cp313-win_amd64.whl", hash = "sha256:2efb84d6789352d7950b03d7f866e6d180284bc02c7e12cb37b489b7083d81aa"}, - {file = "debugpy-1.8.7-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:4b908291a1d051ef3331484de8e959ef3e66f12b5e610c203b5b75d2725613a7"}, - {file = "debugpy-1.8.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da8df5b89a41f1fd31503b179d0a84a5fdb752dddd5b5388dbd1ae23cda31ce9"}, - {file = "debugpy-1.8.7-cp38-cp38-win32.whl", hash = "sha256:b12515e04720e9e5c2216cc7086d0edadf25d7ab7e3564ec8b4521cf111b4f8c"}, - {file = "debugpy-1.8.7-cp38-cp38-win_amd64.whl", hash = "sha256:93176e7672551cb5281577cdb62c63aadc87ec036f0c6a486f0ded337c504596"}, - {file = "debugpy-1.8.7-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:90d93e4f2db442f8222dec5ec55ccfc8005821028982f1968ebf551d32b28907"}, - {file = "debugpy-1.8.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6db2a370e2700557a976eaadb16243ec9c91bd46f1b3bb15376d7aaa7632c81"}, - {file = "debugpy-1.8.7-cp39-cp39-win32.whl", hash = "sha256:a6cf2510740e0c0b4a40330640e4b454f928c7b99b0c9dbf48b11efba08a8cda"}, - {file = "debugpy-1.8.7-cp39-cp39-win_amd64.whl", hash = "sha256:6a9d9d6d31846d8e34f52987ee0f1a904c7baa4912bf4843ab39dadf9b8f3e0d"}, - {file = "debugpy-1.8.7-py2.py3-none-any.whl", hash = "sha256:57b00de1c8d2c84a61b90880f7e5b6deaf4c312ecbde3a0e8912f2a56c4ac9ae"}, - {file = "debugpy-1.8.7.zip", hash = "sha256:18b8f731ed3e2e1df8e9cdaa23fb1fc9c24e570cd0081625308ec51c82efe42e"}, + {file = "debugpy-1.8.8-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:e59b1607c51b71545cb3496876544f7186a7a27c00b436a62f285603cc68d1c6"}, + {file = "debugpy-1.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6531d952b565b7cb2fbd1ef5df3d333cf160b44f37547a4e7cf73666aca5d8d"}, + {file = "debugpy-1.8.8-cp310-cp310-win32.whl", hash = "sha256:b01f4a5e5c5fb1d34f4ccba99a20ed01eabc45a4684f4948b5db17a319dfb23f"}, + {file = "debugpy-1.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:535f4fb1c024ddca5913bb0eb17880c8f24ba28aa2c225059db145ee557035e9"}, + {file = "debugpy-1.8.8-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:c399023146e40ae373753a58d1be0a98bf6397fadc737b97ad612886b53df318"}, + {file = "debugpy-1.8.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09cc7b162586ea2171eea055985da2702b0723f6f907a423c9b2da5996ad67ba"}, + {file = "debugpy-1.8.8-cp311-cp311-win32.whl", hash = "sha256:eea8821d998ebeb02f0625dd0d76839ddde8cbf8152ebbe289dd7acf2cdc6b98"}, + {file = "debugpy-1.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:d4483836da2a533f4b1454dffc9f668096ac0433de855f0c22cdce8c9f7e10c4"}, + {file = "debugpy-1.8.8-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:0cc94186340be87b9ac5a707184ec8f36547fb66636d1029ff4f1cc020e53996"}, + {file = "debugpy-1.8.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64674e95916e53c2e9540a056e5f489e0ad4872645399d778f7c598eacb7b7f9"}, + {file = "debugpy-1.8.8-cp312-cp312-win32.whl", hash = "sha256:5c6e885dbf12015aed73770f29dec7023cb310d0dc2ba8bfbeb5c8e43f80edc9"}, + {file = "debugpy-1.8.8-cp312-cp312-win_amd64.whl", hash = "sha256:19ffbd84e757a6ca0113574d1bf5a2298b3947320a3e9d7d8dc3377f02d9f864"}, + {file = "debugpy-1.8.8-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:705cd123a773d184860ed8dae99becd879dfec361098edbefb5fc0d3683eb804"}, + {file = "debugpy-1.8.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890fd16803f50aa9cb1a9b9b25b5ec321656dd6b78157c74283de241993d086f"}, + {file = "debugpy-1.8.8-cp313-cp313-win32.whl", hash = "sha256:90244598214bbe704aa47556ec591d2f9869ff9e042e301a2859c57106649add"}, + {file = "debugpy-1.8.8-cp313-cp313-win_amd64.whl", hash = "sha256:4b93e4832fd4a759a0c465c967214ed0c8a6e8914bced63a28ddb0dd8c5f078b"}, + {file = "debugpy-1.8.8-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:143ef07940aeb8e7316de48f5ed9447644da5203726fca378f3a6952a50a9eae"}, + {file = "debugpy-1.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f95651bdcbfd3b27a408869a53fbefcc2bcae13b694daee5f1365b1b83a00113"}, + {file = "debugpy-1.8.8-cp38-cp38-win32.whl", hash = "sha256:26b461123a030e82602a750fb24d7801776aa81cd78404e54ab60e8b5fecdad5"}, + {file = "debugpy-1.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3cbf1833e644a3100eadb6120f25be8a532035e8245584c4f7532937edc652a"}, + {file = "debugpy-1.8.8-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:53709d4ec586b525724819dc6af1a7703502f7e06f34ded7157f7b1f963bb854"}, + {file = "debugpy-1.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a9c013077a3a0000e83d97cf9cc9328d2b0bbb31f56b0e99ea3662d29d7a6a2"}, + {file = "debugpy-1.8.8-cp39-cp39-win32.whl", hash = "sha256:ffe94dd5e9a6739a75f0b85316dc185560db3e97afa6b215628d1b6a17561cb2"}, + {file = "debugpy-1.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5c0e5a38c7f9b481bf31277d2f74d2109292179081f11108e668195ef926c0f9"}, + {file = "debugpy-1.8.8-py2.py3-none-any.whl", hash = "sha256:ec684553aba5b4066d4de510859922419febc710df7bba04fe9e7ef3de15d34f"}, + {file = "debugpy-1.8.8.zip", hash = "sha256:e6355385db85cbd666be703a96ab7351bc9e6c61d694893206f8001e22aee091"}, ] [[package]] @@ -1387,7 +1362,7 @@ files = [ name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, @@ -1470,7 +1445,7 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth name = "fastjsonschema" version = "2.20.0" description = "Fastest Python implementation of JSON schema" -optional = false +optional = true python-versions = "*" files = [ {file = "fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"}, @@ -1498,59 +1473,61 @@ typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "fonttools" -version = "4.54.1" +version = "4.55.0" description = "Tools to manipulate font files" -optional = false +optional = true python-versions = ">=3.8" files = [ - {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, - {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, - {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, - {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, - {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, - {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, - {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, - {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, - {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, - {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, - {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, - {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, - {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, - {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, - {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, - {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, - {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, - {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, + {file = "fonttools-4.55.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:51c029d4c0608a21a3d3d169dfc3fb776fde38f00b35ca11fdab63ba10a16f61"}, + {file = "fonttools-4.55.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bca35b4e411362feab28e576ea10f11268b1aeed883b9f22ed05675b1e06ac69"}, + {file = "fonttools-4.55.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ce4ba6981e10f7e0ccff6348e9775ce25ffadbee70c9fd1a3737e3e9f5fa74f"}, + {file = "fonttools-4.55.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31d00f9852a6051dac23294a4cf2df80ced85d1d173a61ba90a3d8f5abc63c60"}, + {file = "fonttools-4.55.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e198e494ca6e11f254bac37a680473a311a88cd40e58f9cc4dc4911dfb686ec6"}, + {file = "fonttools-4.55.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7208856f61770895e79732e1dcbe49d77bd5783adf73ae35f87fcc267df9db81"}, + {file = "fonttools-4.55.0-cp310-cp310-win32.whl", hash = "sha256:e7e6a352ff9e46e8ef8a3b1fe2c4478f8a553e1b5a479f2e899f9dc5f2055880"}, + {file = "fonttools-4.55.0-cp310-cp310-win_amd64.whl", hash = "sha256:636caaeefe586d7c84b5ee0734c1a5ab2dae619dc21c5cf336f304ddb8f6001b"}, + {file = "fonttools-4.55.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fa34aa175c91477485c44ddfbb51827d470011e558dfd5c7309eb31bef19ec51"}, + {file = "fonttools-4.55.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:37dbb3fdc2ef7302d3199fb12468481cbebaee849e4b04bc55b77c24e3c49189"}, + {file = "fonttools-4.55.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5263d8e7ef3c0ae87fbce7f3ec2f546dc898d44a337e95695af2cd5ea21a967"}, + {file = "fonttools-4.55.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f307f6b5bf9e86891213b293e538d292cd1677e06d9faaa4bf9c086ad5f132f6"}, + {file = "fonttools-4.55.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f0a4b52238e7b54f998d6a56b46a2c56b59c74d4f8a6747fb9d4042190f37cd3"}, + {file = "fonttools-4.55.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3e569711464f777a5d4ef522e781dc33f8095ab5efd7548958b36079a9f2f88c"}, + {file = "fonttools-4.55.0-cp311-cp311-win32.whl", hash = "sha256:2b3ab90ec0f7b76c983950ac601b58949f47aca14c3f21eed858b38d7ec42b05"}, + {file = "fonttools-4.55.0-cp311-cp311-win_amd64.whl", hash = "sha256:aa046f6a63bb2ad521004b2769095d4c9480c02c1efa7d7796b37826508980b6"}, + {file = "fonttools-4.55.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:838d2d8870f84fc785528a692e724f2379d5abd3fc9dad4d32f91cf99b41e4a7"}, + {file = "fonttools-4.55.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f46b863d74bab7bb0d395f3b68d3f52a03444964e67ce5c43ce43a75efce9246"}, + {file = "fonttools-4.55.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33b52a9cfe4e658e21b1f669f7309b4067910321757fec53802ca8f6eae96a5a"}, + {file = "fonttools-4.55.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:732a9a63d6ea4a81b1b25a1f2e5e143761b40c2e1b79bb2b68e4893f45139a40"}, + {file = "fonttools-4.55.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7dd91ac3fcb4c491bb4763b820bcab6c41c784111c24172616f02f4bc227c17d"}, + {file = "fonttools-4.55.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1f0e115281a32ff532118aa851ef497a1b7cda617f4621c1cdf81ace3e36fb0c"}, + {file = "fonttools-4.55.0-cp312-cp312-win32.whl", hash = "sha256:6c99b5205844f48a05cb58d4a8110a44d3038c67ed1d79eb733c4953c628b0f6"}, + {file = "fonttools-4.55.0-cp312-cp312-win_amd64.whl", hash = "sha256:f8c8c76037d05652510ae45be1cd8fb5dd2fd9afec92a25374ac82255993d57c"}, + {file = "fonttools-4.55.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8118dc571921dc9e4b288d9cb423ceaf886d195a2e5329cc427df82bba872cd9"}, + {file = "fonttools-4.55.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01124f2ca6c29fad4132d930da69158d3f49b2350e4a779e1efbe0e82bd63f6c"}, + {file = "fonttools-4.55.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ffd58d2691f11f7c8438796e9f21c374828805d33e83ff4b76e4635633674c"}, + {file = "fonttools-4.55.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5435e5f1eb893c35c2bc2b9cd3c9596b0fcb0a59e7a14121562986dd4c47b8dd"}, + {file = "fonttools-4.55.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d12081729280c39d001edd0f4f06d696014c26e6e9a0a55488fabc37c28945e4"}, + {file = "fonttools-4.55.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7ad1f1b98ab6cb927ab924a38a8649f1ffd7525c75fe5b594f5dab17af70e18"}, + {file = "fonttools-4.55.0-cp313-cp313-win32.whl", hash = "sha256:abe62987c37630dca69a104266277216de1023cf570c1643bb3a19a9509e7a1b"}, + {file = "fonttools-4.55.0-cp313-cp313-win_amd64.whl", hash = "sha256:2863555ba90b573e4201feaf87a7e71ca3b97c05aa4d63548a4b69ea16c9e998"}, + {file = "fonttools-4.55.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00f7cf55ad58a57ba421b6a40945b85ac7cc73094fb4949c41171d3619a3a47e"}, + {file = "fonttools-4.55.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f27526042efd6f67bfb0cc2f1610fa20364396f8b1fc5edb9f45bb815fb090b2"}, + {file = "fonttools-4.55.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e67974326af6a8879dc2a4ec63ab2910a1c1a9680ccd63e4a690950fceddbe"}, + {file = "fonttools-4.55.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61dc0a13451143c5e987dec5254d9d428f3c2789a549a7cf4f815b63b310c1cc"}, + {file = "fonttools-4.55.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b2e526b325a903868c62155a6a7e24df53f6ce4c5c3160214d8fe1be2c41b478"}, + {file = "fonttools-4.55.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b7ef9068a1297714e6fefe5932c33b058aa1d45a2b8be32a4c6dee602ae22b5c"}, + {file = "fonttools-4.55.0-cp38-cp38-win32.whl", hash = "sha256:55718e8071be35dff098976bc249fc243b58efa263768c611be17fe55975d40a"}, + {file = "fonttools-4.55.0-cp38-cp38-win_amd64.whl", hash = "sha256:553bd4f8cc327f310c20158e345e8174c8eed49937fb047a8bda51daf2c353c8"}, + {file = "fonttools-4.55.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f901cef813f7c318b77d1c5c14cf7403bae5cb977cede023e22ba4316f0a8f6"}, + {file = "fonttools-4.55.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c9679fc0dd7e8a5351d321d8d29a498255e69387590a86b596a45659a39eb0d"}, + {file = "fonttools-4.55.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2820a8b632f3307ebb0bf57948511c2208e34a4939cf978333bc0a3f11f838"}, + {file = "fonttools-4.55.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23bbbb49bec613a32ed1b43df0f2b172313cee690c2509f1af8fdedcf0a17438"}, + {file = "fonttools-4.55.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a656652e1f5d55b9728937a7e7d509b73d23109cddd4e89ee4f49bde03b736c6"}, + {file = "fonttools-4.55.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f50a1f455902208486fbca47ce33054208a4e437b38da49d6721ce2fef732fcf"}, + {file = "fonttools-4.55.0-cp39-cp39-win32.whl", hash = "sha256:161d1ac54c73d82a3cded44202d0218ab007fde8cf194a23d3dd83f7177a2f03"}, + {file = "fonttools-4.55.0-cp39-cp39-win_amd64.whl", hash = "sha256:ca7fd6987c68414fece41c96836e945e1f320cda56fc96ffdc16e54a44ec57a2"}, + {file = "fonttools-4.55.0-py3-none-any.whl", hash = "sha256:12db5888cd4dd3fcc9f0ee60c6edd3c7e1fd44b7dd0f31381ea03df68f8a153f"}, + {file = "fonttools-4.55.0.tar.gz", hash = "sha256:7636acc6ab733572d5e7eec922b254ead611f1cdad17be3f0be7418e8bfaca71"}, ] [package.extras] @@ -1571,7 +1548,7 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] name = "fqdn" version = "1.5.1" description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" -optional = false +optional = true python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" files = [ {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, @@ -1582,7 +1559,7 @@ files = [ name = "frozenlist" version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, @@ -1683,7 +1660,7 @@ files = [ name = "geomdl" version = "5.3.1" description = "Object-oriented B-Spline and NURBS evaluation library" -optional = false +optional = true python-versions = "*" files = [ {file = "geomdl-5.3.1-py2.py3-none-any.whl", hash = "sha256:0f36a4bacde5b218c73aadc69ff152e7f7fb3aa7260df0e6647a701a5351d76a"}, @@ -1692,13 +1669,13 @@ files = [ [[package]] name = "google-api-core" -version = "2.22.0" +version = "2.23.0" description = "Google API client core library" -optional = false +optional = true python-versions = ">=3.7" files = [ - {file = "google_api_core-2.22.0-py3-none-any.whl", hash = "sha256:a6652b6bd51303902494998626653671703c420f6f4c88cfd3f50ed723e9d021"}, - {file = "google_api_core-2.22.0.tar.gz", hash = "sha256:26f8d76b96477db42b55fd02a33aae4a42ec8b86b98b94969b7333a2c828bf35"}, + {file = "google_api_core-2.23.0-py3-none-any.whl", hash = "sha256:c20100d4c4c41070cf365f1d8ddf5365915291b5eb11b83829fbd1c999b5122f"}, + {file = "google_api_core-2.23.0.tar.gz", hash = "sha256:2ceb087315e6af43f256704b871d99326b1f12a9d6ce99beaedec99ba26a0ace"}, ] [package.dependencies] @@ -1716,13 +1693,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.151.0" +version = "2.153.0" description = "Google API Client Library for Python" -optional = false +optional = true python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.151.0-py2.py3-none-any.whl", hash = "sha256:4427b2f47cd88b0355d540c2c52215f68c337f3bc9d6aae1ceeae4525977504c"}, - {file = "google_api_python_client-2.151.0.tar.gz", hash = "sha256:a9d26d630810ed4631aea21d1de3e42072f98240aaf184a8a1a874a371115034"}, + {file = "google_api_python_client-2.153.0-py2.py3-none-any.whl", hash = "sha256:6ff13bbfa92a57972e33ec3808e18309e5981b8ca1300e5da23bf2b4d6947384"}, + {file = "google_api_python_client-2.153.0.tar.gz", hash = "sha256:35cce8647f9c163fc04fb4d811fc91aae51954a2bdd74918decbe0e65d791dd2"}, ] [package.dependencies] @@ -1736,7 +1713,7 @@ uritemplate = ">=3.0.1,<5" name = "google-auth" version = "2.36.0" description = "Google Authentication Library" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb"}, @@ -1759,7 +1736,7 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] name = "google-auth-httplib2" version = "0.2.0" description = "Google Authentication Library: httplib2 transport" -optional = false +optional = true python-versions = "*" files = [ {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, @@ -1772,13 +1749,13 @@ httplib2 = ">=0.19.0" [[package]] name = "googleapis-common-protos" -version = "1.65.0" +version = "1.66.0" description = "Common protobufs used in Google APIs" -optional = false +optional = true python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, - {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, + {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, + {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, ] [package.dependencies] @@ -1789,70 +1766,70 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "grpcio" -version = "1.67.1" +version = "1.68.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f"}, - {file = "grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311"}, - {file = "grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed"}, - {file = "grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e"}, - {file = "grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb"}, - {file = "grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970"}, - {file = "grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744"}, - {file = "grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5"}, - {file = "grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953"}, - {file = "grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38"}, - {file = "grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78"}, - {file = "grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc"}, - {file = "grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b"}, - {file = "grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb"}, - {file = "grpcio-1.67.1-cp313-cp313-win32.whl", hash = "sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121"}, - {file = "grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba"}, - {file = "grpcio-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65"}, - {file = "grpcio-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce"}, - {file = "grpcio-1.67.1-cp38-cp38-win32.whl", hash = "sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46"}, - {file = "grpcio-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771"}, - {file = "grpcio-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335"}, - {file = "grpcio-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f"}, - {file = "grpcio-1.67.1-cp39-cp39-win32.whl", hash = "sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e"}, - {file = "grpcio-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98"}, - {file = "grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732"}, + {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"}, + {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"}, + {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"}, + {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"}, + {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"}, + {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"}, + {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"}, + {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"}, + {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"}, + {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"}, + {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"}, + {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"}, + {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"}, + {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"}, + {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"}, + {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"}, + {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"}, + {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"}, + {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = "sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"}, + {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"}, + {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"}, + {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"}, + {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = "sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"}, + {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"}, + {file = "grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.67.1)"] +protobuf = ["grpcio-tools (>=1.68.0)"] [[package]] name = "grpcio-health-checking" @@ -1873,7 +1850,7 @@ protobuf = ">=4.21.6" name = "httplib2" version = "0.22.0" description = "A comprehensive HTTP client library." -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, @@ -1918,13 +1895,13 @@ zoneinfo = ["tzdata (>=2024.2)"] [[package]] name = "identify" -version = "2.6.1" +version = "2.6.2" description = "File identification library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "identify-2.6.2-py2.py3-none-any.whl", hash = "sha256:c097384259f49e372f4ea00a19719d95ae27dd5ff0fd77ad630aa891306b82f3"}, + {file = "identify-2.6.2.tar.gz", hash = "sha256:fab5c716c24d7a789775228823797296a2994b075fb6080ac83a102772a98cbd"}, ] [package.extras] @@ -2064,7 +2041,7 @@ test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "num name = "ipywidgets" version = "8.1.5" description = "Jupyter interactive widgets" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, @@ -2085,7 +2062,7 @@ test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] name = "isoduration" version = "20.11.0" description = "Operations with ISO 8601 durations" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, @@ -2097,22 +2074,22 @@ arrow = ">=0.15.0" [[package]] name = "jedi" -version = "0.19.1" +version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] [package.dependencies] -parso = ">=0.8.3,<0.9.0" +parso = ">=0.8.4,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" @@ -2135,7 +2112,7 @@ i18n = ["Babel (>=2.7)"] name = "jsonpointer" version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, @@ -2146,7 +2123,7 @@ files = [ name = "jsonschema" version = "4.23.0" description = "An implementation of JSON Schema validation for Python" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, @@ -2175,7 +2152,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "jsonschema-specifications" version = "2024.10.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -optional = false +optional = true python-versions = ">=3.9" files = [ {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, @@ -2231,7 +2208,7 @@ test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout" name = "jupyter-events" version = "0.10.0" description = "Jupyter Event System library" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "jupyter_events-0.10.0-py3-none-any.whl", hash = "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960"}, @@ -2256,7 +2233,7 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p name = "jupyter-server" version = "2.14.2" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "jupyter_server-2.14.2-py3-none-any.whl", hash = "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd"}, @@ -2292,7 +2269,7 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0,<9)", "pytest-console name = "jupyter-server-proxy" version = "4.4.0" description = "A Jupyter server extension to run additional processes and proxy to them that comes bundled JupyterLab extension to launch pre-defined processes." -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "jupyter_server_proxy-4.4.0-py3-none-any.whl", hash = "sha256:707b5c84810bb8863d50f6c6d50a386fec216149e11802b7d4c451b54a63a9a6"}, @@ -2316,7 +2293,7 @@ test = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-html"] name = "jupyter-server-terminals" version = "0.5.3" description = "A Jupyter Server Extension Providing Terminals." -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"}, @@ -2335,7 +2312,7 @@ test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (> name = "jupyterlab-pygments" version = "0.3.0" description = "Pygments theme using JupyterLab CSS variables" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, @@ -2346,7 +2323,7 @@ files = [ name = "jupyterlab-widgets" version = "3.0.13" description = "Jupyter interactive widgets for JupyterLab" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, @@ -2357,7 +2334,7 @@ files = [ name = "kiwisolver" version = "1.4.7" description = "A fast implementation of the Cassowary constraint solver" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, @@ -2561,7 +2538,7 @@ files = [ name = "matplotlib" version = "3.9.2" description = "Python plotting package" -optional = false +optional = true python-versions = ">=3.9" files = [ {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, @@ -2638,7 +2615,7 @@ traitlets = "*" name = "mistune" version = "3.0.2" description = "A sane and fast Markdown parser with useful plugins and renderers" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, @@ -2649,7 +2626,7 @@ files = [ name = "more-itertools" version = "10.5.0" description = "More routines for operating on iterables, beyond itertools" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, @@ -2660,7 +2637,7 @@ files = [ name = "msgpack" version = "1.1.0" description = "MessagePack serializer" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, @@ -2733,7 +2710,7 @@ files = [ name = "multidict" version = "6.1.0" description = "multidict implementation" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, @@ -2901,7 +2878,7 @@ files = [ name = "nbclient" version = "0.10.0" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." -optional = false +optional = true python-versions = ">=3.8.0" files = [ {file = "nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f"}, @@ -2923,7 +2900,7 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= name = "nbconvert" version = "7.16.4" description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"}, @@ -2960,7 +2937,7 @@ webpdf = ["playwright"] name = "nbformat" version = "5.10.4" description = "The Jupyter Notebook format" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, @@ -3088,7 +3065,7 @@ test = ["matplotlib", "pytest", "pytest-cov"] name = "overrides" version = "7.7.0" description = "A decorator to automatically detect mismatch when overriding a method." -optional = false +optional = true python-versions = ">=3.6" files = [ {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, @@ -3110,7 +3087,7 @@ files = [ name = "pandocfilters" version = "1.5.1" description = "Utilities for writing pandoc filters in python" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, @@ -3298,7 +3275,7 @@ testing = ["pytest", "pytest-benchmark"] name = "pooch" version = "1.8.2" description = "A friend to fetch your data files" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47"}, @@ -3337,7 +3314,7 @@ virtualenv = ">=20.10.0" name = "prometheus-client" version = "0.21.0" description = "Python client for the Prometheus monitoring system." -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"}, @@ -3365,7 +3342,7 @@ wcwidth = "*" name = "propcache" version = "0.2.0" description = "Accelerated property cache" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, @@ -3472,7 +3449,7 @@ files = [ name = "proto-plus" version = "1.25.0" description = "Beautiful, Pythonic protocol buffers." -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, @@ -3575,7 +3552,7 @@ files = [ name = "pyansys-tools-versioning" version = "0.6.0" description = "PyAnsys Tools Versioning." -optional = false +optional = true python-versions = "<4,>=3.10" files = [ {file = "pyansys_tools_versioning-0.6.0-py3-none-any.whl", hash = "sha256:a7191203ccd89ce86a5413e268b3a51127a5b9f5117dba909422bcfdf6e7f81f"}, @@ -3590,7 +3567,7 @@ tests = ["hypothesis (==6.111.0)", "pytest (==8.3.2)", "pytest-cov (==5.0.0)"] name = "pyasn1" version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, @@ -3601,7 +3578,7 @@ files = [ name = "pyasn1-modules" version = "0.4.1" description = "A collection of ASN.1-based protocols modules" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, @@ -3667,7 +3644,7 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pyiges" version = "0.3.1" description = "Pythonic IGES reader" -optional = false +optional = true python-versions = "*" files = [ {file = "pyiges-0.3.1-py3-none-any.whl", hash = "sha256:74a89874649bc7cab139e1d8198cb8cb895537ff8693702d6feb9fb49d402ab5"}, @@ -3698,7 +3675,7 @@ files = [ name = "pyparsing" version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false +optional = true python-versions = ">=3.9" files = [ {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, @@ -3802,7 +3779,7 @@ six = ">=1.5" name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" -optional = false +optional = true python-versions = ">=3.6" files = [ {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, @@ -3813,7 +3790,7 @@ files = [ name = "pyvista" version = "0.44.1" description = "Easier Pythonic interface to VTK" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "pyvista-0.44.1-py3-none-any.whl", hash = "sha256:7a80e8114220ca36d57a4def8e6a3067c908b53b62aa426ea76c76069bb6d1c0"}, @@ -3874,7 +3851,7 @@ files = [ name = "pywinpty" version = "2.0.14" description = "Pseudo terminal support for Windows from Python." -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "pywinpty-2.0.14-cp310-none-win_amd64.whl", hash = "sha256:0b149c2918c7974f575ba79f5a4aad58bd859a52fa9eb1296cc22aa412aa411f"}, @@ -4072,7 +4049,7 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "referencing" version = "0.35.1" description = "JSON Referencing + Python" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, @@ -4108,7 +4085,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rfc3339-validator" version = "0.1.4" description = "A pure python RFC3339 validator" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, @@ -4122,7 +4099,7 @@ six = "*" name = "rfc3986-validator" version = "0.1.1" description = "Pure python rfc3986 validator" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, @@ -4133,7 +4110,7 @@ files = [ name = "rpds-py" version = "0.21.0" description = "Python bindings to Rust's persistent data structures (rpds)" -optional = false +optional = true python-versions = ">=3.9" files = [ {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, @@ -4232,7 +4209,7 @@ files = [ name = "rsa" version = "4.9" description = "Pure-Python RSA implementation" -optional = false +optional = true python-versions = ">=3.6,<4" files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, @@ -4246,7 +4223,7 @@ pyasn1 = ">=0.1.3" name = "scipy" version = "1.14.1" description = "Fundamental algorithms for scientific computing in Python" -optional = false +optional = true python-versions = ">=3.10" files = [ {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, @@ -4296,7 +4273,7 @@ test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.3 name = "scooby" version = "0.10.0" description = "A Great Dane turned Python environment detective" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "scooby-0.10.0-py3-none-any.whl", hash = "sha256:0a3d7e304f8ebb16f69ff7f6360c345d7f50b45f2ddbf7c3d18a6a0dc2cb03a6"}, @@ -4310,7 +4287,7 @@ cpu = ["mkl", "psutil"] name = "send2trash" version = "1.8.3" description = "Send file to trash natively under Mac OS X, Windows and Linux" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, @@ -4324,29 +4301,29 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "75.3.0" +version = "75.5.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" +optional = true +python-versions = ">=3.9" files = [ - {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, - {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, + {file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"}, + {file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +core = ["importlib-metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] [[package]] name = "simpervisor" version = "1.0.0" description = "Simple async process supervisor" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "simpervisor-1.0.0-py3-none-any.whl", hash = "sha256:3e313318264559beea3f475ead202bc1cd58a2f1288363abb5657d306c5b8388"}, @@ -4371,7 +4348,7 @@ files = [ name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, @@ -4660,7 +4637,7 @@ widechars = ["wcwidth"] name = "terminado" version = "0.18.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, @@ -4681,7 +4658,7 @@ typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] name = "tinycss2" version = "1.4.0" description = "A tiny CSS parser" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, @@ -4697,13 +4674,13 @@ test = ["pytest", "ruff"] [[package]] name = "tomli" -version = "2.0.2" +version = "2.1.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, ] [[package]] @@ -4730,7 +4707,7 @@ files = [ name = "tqdm" version = "4.67.0" description = "Fast, Extensible Progress Meter" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be"}, @@ -4766,7 +4743,7 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, name = "trame" version = "3.7.0" description = "Trame, a framework to build applications in plain Python" -optional = false +optional = true python-versions = "*" files = [ {file = "trame-3.7.0-py3-none-any.whl", hash = "sha256:8af7c0d91749a18e6a4098aced90ff9b7aec3a40fa089e85d6ea8b5623353349"}, @@ -4780,13 +4757,13 @@ wslink = ">=2.1.3" [[package]] name = "trame-client" -version = "3.4.0" +version = "3.5.0" description = "Internal client of trame" -optional = false +optional = true python-versions = "*" files = [ - {file = "trame-client-3.4.0.tar.gz", hash = "sha256:60df253adcf640df41e4e769cfe13f298f4a5b6883a604d603479c7bcd3da6cb"}, - {file = "trame_client-3.4.0-py3-none-any.whl", hash = "sha256:59f45597fe18118d67f702b6d294c3bf75d1e84269dde0c0baa5da1a59d9a453"}, + {file = "trame-client-3.5.0.tar.gz", hash = "sha256:e472255608e00bbb3683a805b97825e819326abb01ae007c3121606355691c25"}, + {file = "trame_client-3.5.0-py3-none-any.whl", hash = "sha256:5eff22c68859f88fe0ff7ad3d814e95f81c105e1296f292a47006571d9f9659f"}, ] [package.extras] @@ -4796,7 +4773,7 @@ test = ["Pillow", "pixelmatch", "pytest", "pytest-xprocess", "seleniumbase"] name = "trame-server" version = "3.2.3" description = "Internal server side implementation of trame" -optional = false +optional = true python-versions = "*" files = [ {file = "trame-server-3.2.3.tar.gz", hash = "sha256:4b5d38c17f6c2e8a7bd4644a1d45e2bd79df9829c4ae24e987633754748311f2"}, @@ -4809,13 +4786,13 @@ wslink = ">=2.2.1,<3" [[package]] name = "trame-vtk" -version = "2.8.11" +version = "2.8.12" description = "VTK widgets for trame" -optional = false +optional = true python-versions = "*" files = [ - {file = "trame-vtk-2.8.11.tar.gz", hash = "sha256:cd8391181fc8dbfee1f24dad2d7377db8aab5b012362108b985da88b4b271ac3"}, - {file = "trame_vtk-2.8.11-py3-none-any.whl", hash = "sha256:e00d2500e692474204bc015ced1f701d6a504a8f68e5f858f67b1fe737160106"}, + {file = "trame-vtk-2.8.12.tar.gz", hash = "sha256:f894e5e48347076dee97bcd745da21a24b421d66486fcefebe4fe8eae76c80c4"}, + {file = "trame_vtk-2.8.12-py3-none-any.whl", hash = "sha256:f8fefd082cfd5c6cf3d7185c6d8b5482e21ecce6031971dbdd8935f1895abc4a"}, ] [package.dependencies] @@ -4823,13 +4800,13 @@ trame-client = "*" [[package]] name = "trame-vuetify" -version = "2.7.1" +version = "2.7.2" description = "Vuetify widgets for trame" -optional = false +optional = true python-versions = "*" files = [ - {file = "trame-vuetify-2.7.1.tar.gz", hash = "sha256:48443910a3a53b9fa040e05b261fce9326fe95309b3798d49f0d6aec84cbfdf8"}, - {file = "trame_vuetify-2.7.1-py3-none-any.whl", hash = "sha256:c6bfe3be49ac8b7aa354e4e2c1d92f2c2e9680eea563f46146eef6968a66c2de"}, + {file = "trame-vuetify-2.7.2.tar.gz", hash = "sha256:b1489207c072345250baaea3b9a4b22fdca480bb58b195110eb779b0f60167b3"}, + {file = "trame_vuetify-2.7.2-py3-none-any.whl", hash = "sha256:08d682d6da82b0325e267b0ad7636cf37f4563acf396aafd55a3a0e21b5213b2"}, ] [package.dependencies] @@ -4850,7 +4827,7 @@ files = [ name = "types-python-dateutil" version = "2.9.0.20241003" description = "Typing stubs for python-dateutil" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446"}, @@ -4872,7 +4849,7 @@ files = [ name = "uri-template" version = "1.3.0" description = "RFC 6570 URI Template Processor" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, @@ -4886,7 +4863,7 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake name = "uritemplate" version = "4.1.1" description = "Implementation of RFC 6570 URI Templates" -optional = false +optional = true python-versions = ">=3.6" files = [ {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, @@ -4934,7 +4911,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "vtk" version = "9.3.1" description = "VTK is an open-source toolkit for 3D computer graphics, image processing, and visualization" -optional = false +optional = true python-versions = "*" files = [ {file = "vtk-9.3.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:c41ed344b9cc90ee9dcfc5967815de272985647d0c8e0a57f0e8b4229bc1b0b9"}, @@ -4984,24 +4961,20 @@ files = [ [[package]] name = "webcolors" -version = "24.8.0" +version = "24.11.1" description = "A library for working with the color formats defined by HTML and CSS." -optional = false -python-versions = ">=3.8" +optional = true +python-versions = ">=3.9" files = [ - {file = "webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"}, - {file = "webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d"}, + {file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"}, + {file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"}, ] -[package.extras] -docs = ["furo", "sphinx", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinxext-opengraph"] -tests = ["coverage[toml]"] - [[package]] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -optional = false +optional = true python-versions = "*" files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, @@ -5012,7 +4985,7 @@ files = [ name = "websocket-client" version = "1.8.0" description = "WebSocket client for Python with low level API options" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, @@ -5028,7 +5001,7 @@ test = ["websockets"] name = "websockets" version = "13.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, @@ -5123,7 +5096,7 @@ files = [ name = "widgetsnbextension" version = "4.0.13" description = "Jupyter interactive widgets for Jupyter Notebook" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, @@ -5134,7 +5107,7 @@ files = [ name = "wslink" version = "2.2.1" description = "Python/JavaScript library for communicating over WebSocket" -optional = false +optional = true python-versions = "*" files = [ {file = "wslink-2.2.1-py3-none-any.whl", hash = "sha256:bfa1ce14b576f4a4ac4478e74c92a1445000f4fbf33923c98ffd1dc922c993e2"}, @@ -5150,93 +5123,93 @@ ssl = ["cryptography"] [[package]] name = "yarl" -version = "1.17.1" +version = "1.17.2" description = "Yet another URL library" -optional = false +optional = true python-versions = ">=3.9" files = [ - {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91"}, - {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da"}, - {file = "yarl-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931"}, - {file = "yarl-1.17.1-cp310-cp310-win32.whl", hash = "sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b"}, - {file = "yarl-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243"}, - {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217"}, - {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988"}, - {file = "yarl-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4"}, - {file = "yarl-1.17.1-cp311-cp311-win32.whl", hash = "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7"}, - {file = "yarl-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3"}, - {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61"}, - {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d"}, - {file = "yarl-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199"}, - {file = "yarl-1.17.1-cp312-cp312-win32.whl", hash = "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96"}, - {file = "yarl-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df"}, - {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488"}, - {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374"}, - {file = "yarl-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258"}, - {file = "yarl-1.17.1-cp313-cp313-win32.whl", hash = "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2"}, - {file = "yarl-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda"}, - {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6"}, - {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159"}, - {file = "yarl-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f"}, - {file = "yarl-1.17.1-cp39-cp39-win32.whl", hash = "sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473"}, - {file = "yarl-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138"}, - {file = "yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06"}, - {file = "yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47"}, + {file = "yarl-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:93771146ef048b34201bfa382c2bf74c524980870bb278e6df515efaf93699ff"}, + {file = "yarl-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8281db240a1616af2f9c5f71d355057e73a1409c4648c8949901396dc0a3c151"}, + {file = "yarl-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:170ed4971bf9058582b01a8338605f4d8c849bd88834061e60e83b52d0c76870"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc61b005f6521fcc00ca0d1243559a5850b9dd1e1fe07b891410ee8fe192d0c0"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871e1b47eec7b6df76b23c642a81db5dd6536cbef26b7e80e7c56c2fd371382e"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a58a2f2ca7aaf22b265388d40232f453f67a6def7355a840b98c2d547bd037f"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:736bb076f7299c5c55dfef3eb9e96071a795cb08052822c2bb349b06f4cb2e0a"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fd51299e21da709eabcd5b2dd60e39090804431292daacbee8d3dabe39a6bc0"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:358dc7ddf25e79e1cc8ee16d970c23faee84d532b873519c5036dbb858965795"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:50d866f7b1a3f16f98603e095f24c0eeba25eb508c85a2c5939c8b3870ba2df8"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8b9c4643e7d843a0dca9cd9d610a0876e90a1b2cbc4c5ba7930a0d90baf6903f"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d63123bfd0dce5f91101e77c8a5427c3872501acece8c90df457b486bc1acd47"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4e76381be3d8ff96a4e6c77815653063e87555981329cf8f85e5be5abf449021"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:734144cd2bd633a1516948e477ff6c835041c0536cef1d5b9a823ae29899665b"}, + {file = "yarl-1.17.2-cp310-cp310-win32.whl", hash = "sha256:26bfb6226e0c157af5da16d2d62258f1ac578d2899130a50433ffee4a5dfa673"}, + {file = "yarl-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:76499469dcc24759399accd85ec27f237d52dec300daaca46a5352fcbebb1071"}, + {file = "yarl-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:792155279dc093839e43f85ff7b9b6493a8eaa0af1f94f1f9c6e8f4de8c63500"}, + {file = "yarl-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38bc4ed5cae853409cb193c87c86cd0bc8d3a70fd2268a9807217b9176093ac6"}, + {file = "yarl-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4a8c83f6fcdc327783bdc737e8e45b2e909b7bd108c4da1892d3bc59c04a6d84"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6d5fed96f0646bfdf698b0a1cebf32b8aae6892d1bec0c5d2d6e2df44e1e2d"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:782ca9c58f5c491c7afa55518542b2b005caedaf4685ec814fadfcee51f02493"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff6af03cac0d1a4c3c19e5dcc4c05252411bf44ccaa2485e20d0a7c77892ab6e"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a3f47930fbbed0f6377639503848134c4aa25426b08778d641491131351c2c8"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1fa68a3c921365c5745b4bd3af6221ae1f0ea1bf04b69e94eda60e57958907f"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:187df91395c11e9f9dc69b38d12406df85aa5865f1766a47907b1cc9855b6303"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:93d1c8cc5bf5df401015c5e2a3ce75a5254a9839e5039c881365d2a9dcfc6dc2"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:11d86c6145ac5c706c53d484784cf504d7d10fa407cb73b9d20f09ff986059ef"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c42774d1d1508ec48c3ed29e7b110e33f5e74a20957ea16197dbcce8be6b52ba"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8e589379ef0407b10bed16cc26e7392ef8f86961a706ade0a22309a45414d7"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1056cadd5e850a1c026f28e0704ab0a94daaa8f887ece8dfed30f88befb87bb0"}, + {file = "yarl-1.17.2-cp311-cp311-win32.whl", hash = "sha256:be4c7b1c49d9917c6e95258d3d07f43cfba2c69a6929816e77daf322aaba6628"}, + {file = "yarl-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:ac8eda86cc75859093e9ce390d423aba968f50cf0e481e6c7d7d63f90bae5c9c"}, + {file = "yarl-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd90238d3a77a0e07d4d6ffdebc0c21a9787c5953a508a2231b5f191455f31e9"}, + {file = "yarl-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c74f0b0472ac40b04e6d28532f55cac8090e34c3e81f118d12843e6df14d0909"}, + {file = "yarl-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d486ddcaca8c68455aa01cf53d28d413fb41a35afc9f6594a730c9779545876"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25b7e93f5414b9a983e1a6c1820142c13e1782cc9ed354c25e933aebe97fcf2"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a0baff7827a632204060f48dca9e63fbd6a5a0b8790c1a2adfb25dc2c9c0d50"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:460024cacfc3246cc4d9f47a7fc860e4fcea7d1dc651e1256510d8c3c9c7cde0"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5870d620b23b956f72bafed6a0ba9a62edb5f2ef78a8849b7615bd9433384171"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2941756754a10e799e5b87e2319bbec481ed0957421fba0e7b9fb1c11e40509f"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9611b83810a74a46be88847e0ea616794c406dbcb4e25405e52bff8f4bee2d0a"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cd7e35818d2328b679a13268d9ea505c85cd773572ebb7a0da7ccbca77b6a52e"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6b981316fcd940f085f646b822c2ff2b8b813cbd61281acad229ea3cbaabeb6b"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:688058e89f512fb7541cb85c2f149c292d3fa22f981d5a5453b40c5da49eb9e8"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56afb44a12b0864d17b597210d63a5b88915d680f6484d8d202ed68ade38673d"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:17931dfbb84ae18b287279c1f92b76a3abcd9a49cd69b92e946035cff06bcd20"}, + {file = "yarl-1.17.2-cp312-cp312-win32.whl", hash = "sha256:ff8d95e06546c3a8c188f68040e9d0360feb67ba8498baf018918f669f7bc39b"}, + {file = "yarl-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:4c840cc11163d3c01a9d8aad227683c48cd3e5be5a785921bcc2a8b4b758c4f3"}, + {file = "yarl-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3294f787a437cb5d81846de3a6697f0c35ecff37a932d73b1fe62490bef69211"}, + {file = "yarl-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1e7fedb09c059efee2533119666ca7e1a2610072076926fa028c2ba5dfeb78c"}, + {file = "yarl-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:da9d3061e61e5ae3f753654813bc1cd1c70e02fb72cf871bd6daf78443e9e2b1"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91c012dceadc695ccf69301bfdccd1fc4472ad714fe2dd3c5ab4d2046afddf29"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f11fd61d72d93ac23718d393d2a64469af40be2116b24da0a4ca6922df26807e"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46c465ad06971abcf46dd532f77560181387b4eea59084434bdff97524444032"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef6eee1a61638d29cd7c85f7fd3ac7b22b4c0fabc8fd00a712b727a3e73b0685"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4434b739a8a101a837caeaa0137e0e38cb4ea561f39cb8960f3b1e7f4967a3fc"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:752485cbbb50c1e20908450ff4f94217acba9358ebdce0d8106510859d6eb19a"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:17791acaa0c0f89323c57da7b9a79f2174e26d5debbc8c02d84ebd80c2b7bff8"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5c6ea72fe619fee5e6b5d4040a451d45d8175f560b11b3d3e044cd24b2720526"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db5ac3871ed76340210fe028f535392f097fb31b875354bcb69162bba2632ef4"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7a1606ba68e311576bcb1672b2a1543417e7e0aa4c85e9e718ba6466952476c0"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9bc27dd5cfdbe3dc7f381b05e6260ca6da41931a6e582267d5ca540270afeeb2"}, + {file = "yarl-1.17.2-cp313-cp313-win32.whl", hash = "sha256:52492b87d5877ec405542f43cd3da80bdcb2d0c2fbc73236526e5f2c28e6db28"}, + {file = "yarl-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:8e1bf59e035534ba4077f5361d8d5d9194149f9ed4f823d1ee29ef3e8964ace3"}, + {file = "yarl-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c556fbc6820b6e2cda1ca675c5fa5589cf188f8da6b33e9fc05b002e603e44fa"}, + {file = "yarl-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f44a4247461965fed18b2573f3a9eb5e2c3cad225201ee858726cde610daca"}, + {file = "yarl-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a3ede8c248f36b60227eb777eac1dbc2f1022dc4d741b177c4379ca8e75571a"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2654caaf5584449d49c94a6b382b3cb4a246c090e72453493ea168b931206a4d"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d41c684f286ce41fa05ab6af70f32d6da1b6f0457459a56cf9e393c1c0b2217"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2270d590997445a0dc29afa92e5534bfea76ba3aea026289e811bf9ed4b65a7f"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18662443c6c3707e2fc7fad184b4dc32dd428710bbe72e1bce7fe1988d4aa654"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75ac158560dec3ed72f6d604c81090ec44529cfb8169b05ae6fcb3e986b325d9"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1fee66b32e79264f428dc8da18396ad59cc48eef3c9c13844adec890cd339db5"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:585ce7cd97be8f538345de47b279b879e091c8b86d9dbc6d98a96a7ad78876a3"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c019abc2eca67dfa4d8fb72ba924871d764ec3c92b86d5b53b405ad3d6aa56b0"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c6e659b9a24d145e271c2faf3fa6dd1fcb3e5d3f4e17273d9e0350b6ab0fe6e2"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:d17832ba39374134c10e82d137e372b5f7478c4cceeb19d02ae3e3d1daed8721"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bc3003710e335e3f842ae3fd78efa55f11a863a89a72e9a07da214db3bf7e1f8"}, + {file = "yarl-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f5ffc6b7ace5b22d9e73b2a4c7305740a339fbd55301d52735f73e21d9eb3130"}, + {file = "yarl-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:48e424347a45568413deec6f6ee2d720de2cc0385019bedf44cd93e8638aa0ed"}, + {file = "yarl-1.17.2-py3-none-any.whl", hash = "sha256:dd7abf4f717e33b7487121faf23560b3a50924f80e4bef62b22dab441ded8f3b"}, + {file = "yarl-1.17.2.tar.gz", hash = "sha256:753eaaa0c7195244c84b5cc159dc8204b7fd99f716f11198f999f2332a86b178"}, ] [package.dependencies] @@ -5246,13 +5219,13 @@ propcache = ">=0.2.0" [[package]] name = "zipp" -version = "3.20.2" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] @@ -5264,9 +5237,11 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", type = ["pytest-mypy"] [extras] +all = ["ansys-dpf-composites", "ansys-dpf-core", "ansys-mapdl-core", "ansys-mechanical-core", "matplotlib", "pyvista", "scipy"] examples = ["ansys-dpf-composites", "ansys-mapdl-core", "ansys-mechanical-core", "matplotlib", "scipy"] +plotting = ["pyvista"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "ad20e91517c633c119dd7df17612c7ccca7e228f016a54999c9a76a0a62c4761" +content-hash = "549b44884d6ece4316b4e6bf59f1c9387463f2e8601053235e4419818e488986" diff --git a/pyproject.toml b/pyproject.toml index 822a257ed9..065247e3e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,11 +41,11 @@ ansys-api-acp = "==0.2.0" ansys-tools-path = ">=0" ansys-tools-local-product-launcher = ">=0.1" ansys-tools-filetransfer = ">=0.1" -pyvista = ">=0.42.0" networkx = ">=3.0.0" -# Dependencies for the examples. Update also the 'dev' group when -# these are updated. +# Optional dependencies which are included in the 'extras' section. +# Update also the 'dev' group when these are updated. +pyvista = { version = ">=0.42.0", optional = true, extras = ["jupyter", "trame"] } ansys-mapdl-core = { version = ">=0.68.3", optional = true } ansys-dpf-composites = { version = ">=0.6", optional = true } ansys-dpf-core = { version = ">=0.13", optional = true} @@ -69,20 +69,10 @@ numpydoc = ">=1.6.0" ansys-sphinx-theme = ">=1.0.0" pypandoc = ">=1.13" sphinx-gallery = ">=0.15.0" -sphinx-design = ">=0.5.0" # undeclared indirect dependency via PyVista +sphinx-design = ">=0.5.0" # undeclared indirect dependency via PyVista sphinx-jinja = ">=2.0.2" ipykernel = ">=6.22.0" -pyvista = { version = ">=0.42.0", extras = ["jupyter", "trame"] } - -# Repeat optional dependencies from the main group which are -# included in the 'examples' extra. This is done s.t. the install -# flag '--with=dev,test' will install all dependencies. -ansys-mapdl-core = ">=0.68.3" -ansys-dpf-composites = { version = ">=0.6"} -ansys-dpf-core = { version = ">=0.13"} -ansys-mechanical-core = { version = ">=0.10.0" } -matplotlib = ">=3.8.3" -scipy = ">=1.12.0" + [tool.poetry.group.test] optional = true @@ -98,6 +88,7 @@ docker = ">=7.0" [tool.poetry.extras] # For the examples, we keep an extra to simplify installing these dependencies for the # end user. +plotting = ["pyvista"] examples = [ "ansys-mapdl-core", "ansys-dpf-composites", @@ -105,6 +96,15 @@ examples = [ "matplotlib", "scipy", ] +all = [ + "pyvista", + "ansys-mapdl-core", + "ansys-dpf-composites", + "ansys-dpf-core", + "ansys-mechanical-core", + "matplotlib", + "scipy", +] [tool.poetry.plugins."ansys.tools.local_product_launcher.launcher"] "ACP.direct" = "ansys.acp.core._server.direct:DirectLauncher" @@ -167,6 +167,7 @@ ignore_missing_imports = true # This section is required even if empty, so that pytest recognizes this # file as a pytest configuration file, and sets the containing directory # as its 'rootdir'. +markers = "plotting" [tool.coverage.run] branch = true diff --git a/src/ansys/acp/core/_model_printer.py b/src/ansys/acp/core/_model_printer.py index 61f43ead20..aac0847418 100644 --- a/src/ansys/acp/core/_model_printer.py +++ b/src/ansys/acp/core/_model_printer.py @@ -23,7 +23,7 @@ import os from ._tree_objects.model import Model -from ._utils.visualization import _replace_underscores_and_capitalize +from ._utils.string_manipulation import replace_underscores_and_capitalize __all__ = ["Node", "print_model", "get_model_tree"] @@ -60,7 +60,7 @@ def _add_tree_part( items = list(getattr(model, container_name).items()) if len(items) == 0: return - container = Node(_replace_underscores_and_capitalize(container_name)) + container = Node(replace_underscores_and_capitalize(container_name)) tree.children.append(container) for entity_name, entity in items: group_node = Node(entity_name) diff --git a/src/ansys/acp/core/_plotter.py b/src/ansys/acp/core/_plotter.py index 743d9c1e97..62ad06c8df 100644 --- a/src/ansys/acp/core/_plotter.py +++ b/src/ansys/acp/core/_plotter.py @@ -23,14 +23,14 @@ from collections.abc import Sequence from typing import TYPE_CHECKING, Any, Optional -import pyvista - if TYPE_CHECKING: # pragma: no cover + import pyvista from ansys.acp.core import Model from ansys.acp.core.mesh_data import MeshData from ansys.acp.core.mesh_data import VectorData -from ansys.acp.core._utils.visualization import _replace_underscores_and_capitalize +from ._utils.pyvista_import_check import requires_pyvista +from ._utils.string_manipulation import replace_underscores_and_capitalize __all__ = ["get_directions_plotter"] @@ -46,6 +46,7 @@ } +@requires_pyvista def get_directions_plotter( *, model: "Model", @@ -54,7 +55,7 @@ def get_directions_plotter( culling_factor: int = 1, length_factor: float = 1.0, **kwargs: Any, -) -> pyvista.Plotter: +) -> "pyvista.Plotter": """Get a pyvista plotter that shows the specified directions on the mesh. Parameters @@ -77,6 +78,8 @@ def get_directions_plotter( kwargs : Keyword arguments passed to the PyVista object constructor. """ + import pyvista + if mesh is None: mesh = model.mesh @@ -95,7 +98,7 @@ def get_directions_plotter( **kwargs, ), color=color, - label=_replace_underscores_and_capitalize(vector_data.component_name), + label=replace_underscores_and_capitalize(vector_data.component_name), ) plotter.add_legend(face=None, bcolor=[0.2, 0.2, 0.2], size=(0.25, 0.25)) return plotter diff --git a/src/ansys/acp/core/_recursive_copy.py b/src/ansys/acp/core/_recursive_copy.py index ce9ce2a11b..c21c5b5e90 100644 --- a/src/ansys/acp/core/_recursive_copy.py +++ b/src/ansys/acp/core/_recursive_copy.py @@ -29,8 +29,8 @@ from ._tree_objects import LookUpTable1D, LookUpTable1DColumn, LookUpTable3D, LookUpTable3DColumn from ._tree_objects._grpc_helpers.linked_object_helpers import get_linked_paths from ._tree_objects.base import CreatableTreeObject, TreeObject -from ._typing_helper import StrEnum from ._utils.resource_paths import common_path, to_parts +from ._utils.typing_helper import StrEnum __all__ = ["recursive_copy", "LinkedObjectHandling"] diff --git a/src/ansys/acp/core/_server/acp_instance.py b/src/ansys/acp/core/_server/acp_instance.py index 7db71a4ab0..fe4c8716ce 100644 --- a/src/ansys/acp/core/_server/acp_instance.py +++ b/src/ansys/acp/core/_server/acp_instance.py @@ -36,7 +36,7 @@ from .._tree_objects import Model from .._tree_objects._grpc_helpers.exceptions import wrap_grpc_errors from .._tree_objects.base import ServerWrapper -from .._typing_helper import PATH as _PATH +from .._utils.typing_helper import PATH as _PATH from .common import ServerProtocol __all__ = ["ACPInstance"] diff --git a/src/ansys/acp/core/_server/common.py b/src/ansys/acp/core/_server/common.py index d4e177b6cf..5992a4d869 100644 --- a/src/ansys/acp/core/_server/common.py +++ b/src/ansys/acp/core/_server/common.py @@ -26,7 +26,7 @@ import grpc -from .._typing_helper import StrEnum +from .._utils.typing_helper import StrEnum __all__ = ["LaunchMode"] diff --git a/src/ansys/acp/core/_server/docker_compose.py b/src/ansys/acp/core/_server/docker_compose.py index 47b195952d..3cb8c13eee 100644 --- a/src/ansys/acp/core/_server/docker_compose.py +++ b/src/ansys/acp/core/_server/docker_compose.py @@ -56,9 +56,6 @@ def _get_default_license_server() -> str: return "" -_COMPOSE_FILE_DEFAULT_KEY = "default" - - @dataclasses.dataclass class DockerComposeLaunchConfig: """Configuration options for launching ACP through docker compose.""" diff --git a/src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py b/src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py index d921e9cad9..94bee0d680 100644 --- a/src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py +++ b/src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py @@ -28,14 +28,16 @@ import numpy as np import numpy.typing as npt -from pyvista.core.pointset import PolyData, UnstructuredGrid from typing_extensions import Self +if typing.TYPE_CHECKING: # pragma: no cover + from pyvista.core.pointset import PolyData, UnstructuredGrid + from ansys.acp.core._utils.array_conversions import dataarray_to_numpy, to_numpy from ansys.api.acp.v0 import mesh_query_pb2, mesh_query_pb2_grpc -from .._typing_helper import StrEnum from .._utils.property_protocols import ReadOnlyProperty +from .._utils.typing_helper import StrEnum from ._mesh_data import MeshData from .base import TreeObject from .enums import ( diff --git a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py index b87cf98946..d4f10ed147 100644 --- a/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py +++ b/src/ansys/acp/core/_tree_objects/_grpc_helpers/enum_wrapper.py @@ -26,7 +26,7 @@ __all__ = ["wrap_to_string_enum"] -from ansys.acp.core._typing_helper import StrEnum +from ansys.acp.core._utils.typing_helper import StrEnum # mypy doesn't understand this dynamically created Enum, so we have to # fall back to 'Any'. diff --git a/src/ansys/acp/core/_tree_objects/_mesh_data.py b/src/ansys/acp/core/_tree_objects/_mesh_data.py index 0eefcb66ab..f622a3e69d 100644 --- a/src/ansys/acp/core/_tree_objects/_mesh_data.py +++ b/src/ansys/acp/core/_tree_objects/_mesh_data.py @@ -23,17 +23,20 @@ from __future__ import annotations import dataclasses +import typing import numpy as np import numpy.typing as npt from packaging.version import parse as parse_version -from pyvista.core.pointset import UnstructuredGrid + +if typing.TYPE_CHECKING: # pragma: no cover + from pyvista.core.pointset import UnstructuredGrid from ansys.api.acp.v0 import base_pb2, mesh_query_pb2, mesh_query_pb2_grpc from .._utils.array_conversions import to_numpy from .._utils.property_protocols import ReadOnlyProperty -from .._utils.visualization import to_pyvista_faces, to_pyvista_types +from .._utils.pyvista_import_check import requires_pyvista from .base import TreeObject __all__ = [ @@ -55,8 +58,13 @@ class MeshData: element_nodes: npt.NDArray[np.int32] element_nodes_offsets: npt.NDArray[np.int32] + @requires_pyvista def to_pyvista(self) -> UnstructuredGrid: """Convert the mesh data to a PyVista mesh.""" + from pyvista.core.pointset import UnstructuredGrid + + from .._utils.visualization import to_pyvista_faces, to_pyvista_types + return UnstructuredGrid( to_pyvista_faces( element_types=self.element_types, diff --git a/src/ansys/acp/core/_tree_objects/_solid_model_export.py b/src/ansys/acp/core/_tree_objects/_solid_model_export.py index dfca22caf3..cde63b9e6d 100644 --- a/src/ansys/acp/core/_tree_objects/_solid_model_export.py +++ b/src/ansys/acp/core/_tree_objects/_solid_model_export.py @@ -26,8 +26,8 @@ from ansys.api.acp.v0 import solid_model_export_pb2 -from .._typing_helper import PATH as _PATH from .._utils.path_to_str import path_to_str_checked +from .._utils.typing_helper import PATH as _PATH from ._grpc_helpers.exceptions import wrap_grpc_errors from .base import CreatableTreeObject from .enums import ( diff --git a/src/ansys/acp/core/_tree_objects/cad_geometry.py b/src/ansys/acp/core/_tree_objects/cad_geometry.py index 53f18a035b..e5512130a1 100644 --- a/src/ansys/acp/core/_tree_objects/cad_geometry.py +++ b/src/ansys/acp/core/_tree_objects/cad_geometry.py @@ -24,13 +24,15 @@ from collections.abc import Iterable import dataclasses -from typing import cast +from typing import TYPE_CHECKING, cast import numpy as np import numpy.typing as npt -import pyvista as pv from typing_extensions import Self +if TYPE_CHECKING: # pragma: no cover + import pyvista + from ansys.api.acp.v0 import ( base_pb2, cad_component_pb2_grpc, @@ -38,10 +40,11 @@ cad_geometry_pb2_grpc, ) -from .._typing_helper import PATH from .._utils.array_conversions import to_numpy from .._utils.path_to_str import path_to_str_checked from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from .._utils.pyvista_import_check import requires_pyvista +from .._utils.typing_helper import PATH from ._grpc_helpers.exceptions import wrap_grpc_errors from ._grpc_helpers.mapping import get_read_only_collection_property from ._grpc_helpers.property_helper import ( @@ -71,11 +74,14 @@ def _from_pb(cls, response: cad_geometry_pb2.TriangleMeshData) -> Self: to_numpy(response.element_nodes), ) + @requires_pyvista def to_pyvista( self, - ) -> pv.PolyData: + ) -> pyvista.PolyData: """Convert the mesh data to a PyVista object.""" - return pv.PolyData.from_regular_faces( + import pyvista + + return pyvista.PolyData.from_regular_faces( points=self.node_coordinates, faces=self.element_nodes, ) diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index 22474f83a4..c5b3075d6f 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -36,8 +36,8 @@ solid_model_pb2, ) -from .._typing_helper import PATH as _PATH from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty +from .._utils.typing_helper import PATH as _PATH from ._elemental_or_nodal_data import ( ElementalData, NodalData, diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 79ac727c9f..0c2282bdd1 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -67,10 +67,10 @@ ) from ansys.api.acp.v0.base_pb2 import CollectionPath -from .._typing_helper import PATH as _PATH from .._utils.path_to_str import path_to_str_checked from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty from .._utils.resource_paths import join as rp_join +from .._utils.typing_helper import PATH as _PATH from ._elemental_or_nodal_data import ( ElementalData, NodalData, diff --git a/src/ansys/acp/core/_utils/path_to_str.py b/src/ansys/acp/core/_utils/path_to_str.py index 10379ea12c..b100a3d829 100644 --- a/src/ansys/acp/core/_utils/path_to_str.py +++ b/src/ansys/acp/core/_utils/path_to_str.py @@ -22,7 +22,7 @@ from pathlib import PurePath -from .._typing_helper import PATH +from .typing_helper import PATH __all__ = ["path_to_str_checked"] diff --git a/src/ansys/acp/core/_utils/pyvista_import_check.py b/src/ansys/acp/core/_utils/pyvista_import_check.py new file mode 100644 index 0000000000..a7df22501c --- /dev/null +++ b/src/ansys/acp/core/_utils/pyvista_import_check.py @@ -0,0 +1,49 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from collections.abc import Callable +from functools import wraps +from typing import ParamSpec, TypeVar + +P = ParamSpec("P") +T = TypeVar("T") + + +def requires_pyvista(func: Callable[P, T]) -> Callable[P, T]: + """Decorate a function as requiring the 'pyvista' package. + + Checks if the 'pyvista' package is installed before executing the + decorated function, and provides a helpful error message if it is not. + """ + + @wraps(func) + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + try: + import pyvista # noqa + except ImportError as exc: + raise ImportError( + f"The '{func.__name__}' function requires the 'pyvista' package. " + "Please reinstall PyACP with 'pip install ansys-acp-core[plotting]'." + ) from exc + return func(*args, **kwargs) + + return inner diff --git a/src/ansys/acp/core/_utils/string_manipulation.py b/src/ansys/acp/core/_utils/string_manipulation.py new file mode 100644 index 0000000000..e630fc66ad --- /dev/null +++ b/src/ansys/acp/core/_utils/string_manipulation.py @@ -0,0 +1,26 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +def replace_underscores_and_capitalize(input_string: str) -> str: + """Replace underscores with spaces and capitalize each word.""" + return " ".join(part.capitalize() for part in input_string.split("_")) diff --git a/src/ansys/acp/core/_typing_helper.py b/src/ansys/acp/core/_utils/typing_helper.py similarity index 100% rename from src/ansys/acp/core/_typing_helper.py rename to src/ansys/acp/core/_utils/typing_helper.py diff --git a/src/ansys/acp/core/_utils/visualization.py b/src/ansys/acp/core/_utils/visualization.py index e99f9758d4..85b512db09 100644 --- a/src/ansys/acp/core/_utils/visualization.py +++ b/src/ansys/acp/core/_utils/visualization.py @@ -154,7 +154,3 @@ def to_pyvista_faces( def to_pyvista_types(element_types: npt.NDArray[np.int32]) -> npt.NDArray[np.int32]: """Convert ACP element types to PyVista cell types.""" return np.array([ELEMENT_TO_PYVISTA_TYPE[el_type] for el_type in element_types]) - - -def _replace_underscores_and_capitalize(input_string: str) -> str: - return " ".join(part.capitalize() for part in input_string.split("_")) diff --git a/src/ansys/acp/core/_workflow.py b/src/ansys/acp/core/_workflow.py index 6e79bedea6..3b80626c8d 100644 --- a/src/ansys/acp/core/_workflow.py +++ b/src/ansys/acp/core/_workflow.py @@ -31,7 +31,7 @@ from ._server import ACPInstance from ._server.common import ServerProtocol from ._tree_objects import Model -from ._typing_helper import PATH +from ._utils.typing_helper import PATH # Avoid dependencies on pydpf-composites and dpf-core if it is not used if typing.TYPE_CHECKING: # pragma: no cover diff --git a/src/ansys/acp/core/mechanical_integration_helpers.py b/src/ansys/acp/core/mechanical_integration_helpers.py index 0c9e8810de..f3de0b6bd4 100644 --- a/src/ansys/acp/core/mechanical_integration_helpers.py +++ b/src/ansys/acp/core/mechanical_integration_helpers.py @@ -30,7 +30,7 @@ if typing.TYPE_CHECKING: import ansys.mechanical.core as pymechanical -from ._typing_helper import PATH +from ._utils.typing_helper import PATH __all__ = [ "export_mesh_for_acp", diff --git a/tests/conftest.py b/tests/conftest.py index d249cdc8cf..b2908df7a3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,7 +43,7 @@ launch_acp, ) from ansys.acp.core._server.common import ServerProtocol -from ansys.acp.core._typing_helper import PATH +from ansys.acp.core._utils.typing_helper import PATH from ansys.tools.local_product_launcher.config import set_config_for __all__ = [ diff --git a/tests/unittests/test_edge_property_list.py b/tests/unittests/test_edge_property_list.py index 40cca5a226..4ff8d446c2 100644 --- a/tests/unittests/test_edge_property_list.py +++ b/tests/unittests/test_edge_property_list.py @@ -28,7 +28,7 @@ import pytest from ansys.acp.core import Fabric, Lamina, SubLaminate -from ansys.acp.core._typing_helper import PATH +from ansys.acp.core._utils.typing_helper import PATH @pytest.fixture diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index b8937f1c86..df9740b693 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -29,7 +29,6 @@ import numpy as np import numpy.testing import pytest -import pyvista from ansys.acp.core import ElementalDataType, UnitSystemType from ansys.acp.core.mesh_data import VectorData @@ -212,14 +211,20 @@ def test_nodal_data(minimal_complete_model): numpy.testing.assert_allclose(data.node_labels.values, np.array([1, 2, 3, 4])) +@pytest.mark.plotting def test_mesh_data_to_pyvista(minimal_complete_model): + import pyvista + pv_mesh = minimal_complete_model.mesh.to_pyvista() assert isinstance(pv_mesh, pyvista.core.pointset.UnstructuredGrid) assert pv_mesh.n_points == 4 assert pv_mesh.n_cells == 1 +@pytest.mark.plotting def test_elemental_data_to_pyvista(minimal_complete_model): + import pyvista + data = minimal_complete_model.elemental_data pv_mesh = data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) assert isinstance(pv_mesh, pyvista.core.pointset.UnstructuredGrid) @@ -227,8 +232,11 @@ def test_elemental_data_to_pyvista(minimal_complete_model): assert pv_mesh.n_cells == 1 +@pytest.mark.plotting @pytest.mark.parametrize("component", [e.value for e in ElementalDataType]) def test_elemental_data_to_pyvista_with_component(minimal_complete_model, component): + import pyvista + data = minimal_complete_model.elemental_data if not hasattr(data, component): pytest.skip(f"Model elemental data does not contain component '{component}'") diff --git a/tests/unittests/test_modeling_ply.py b/tests/unittests/test_modeling_ply.py index 642cd81365..fdeaca27d1 100644 --- a/tests/unittests/test_modeling_ply.py +++ b/tests/unittests/test_modeling_ply.py @@ -23,7 +23,6 @@ import numpy as np import numpy.testing import pytest -import pyvista from ansys.acp.core import ( BooleanOperationType, @@ -342,7 +341,10 @@ def test_nodal_data(simple_modeling_ply): ) +@pytest.mark.plotting def test_elemental_data_to_pyvista(minimal_complete_model, simple_modeling_ply): + import pyvista + elemental_data = simple_modeling_ply.elemental_data pv_mesh = elemental_data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) assert isinstance(pv_mesh, pyvista.core.pointset.UnstructuredGrid) @@ -350,10 +352,13 @@ def test_elemental_data_to_pyvista(minimal_complete_model, simple_modeling_ply): assert pv_mesh.n_cells == 1 +@pytest.mark.plotting @pytest.mark.parametrize("component", [e.value for e in ElementalDataType]) def test_elemental_data_to_pyvista_with_component( minimal_complete_model, simple_modeling_ply, component ): + import pyvista + data = simple_modeling_ply.elemental_data if not hasattr(data, component): pytest.skip(f"Modeling Ply elemental data does not contain component '{component}'") @@ -383,7 +388,10 @@ def test_elemental_data_to_pyvista_with_component( assert pv_mesh.n_cells == 1 +@pytest.mark.plotting def test_nodal_data_to_pyvista(minimal_complete_model, simple_modeling_ply): + import pyvista + data = simple_modeling_ply.nodal_data pv_mesh = data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) assert isinstance(pv_mesh, pyvista.core.pointset.UnstructuredGrid) @@ -391,10 +399,13 @@ def test_nodal_data_to_pyvista(minimal_complete_model, simple_modeling_ply): assert pv_mesh.n_cells == 1 +@pytest.mark.plotting @pytest.mark.parametrize("component", [e.value for e in NodalDataType]) def test_nodal_data_to_pyvista_with_component( minimal_complete_model, simple_modeling_ply, component ): + import pyvista + data = simple_modeling_ply.nodal_data if not hasattr(data, component): pytest.skip(f"Modeling Ply nodal data does not contain component '{component}'") diff --git a/tests/unittests/test_plot_utils.py b/tests/unittests/test_plot_utils.py index 0420ab57f3..e9d87de93c 100644 --- a/tests/unittests/test_plot_utils.py +++ b/tests/unittests/test_plot_utils.py @@ -24,7 +24,7 @@ from pytest_cases import parametrize_with_cases from ansys.acp.core._plotter import get_directions_plotter -from ansys.acp.core._utils.visualization import _replace_underscores_and_capitalize +from ansys.acp.core._utils.string_manipulation import replace_underscores_and_capitalize @pytest.fixture @@ -51,6 +51,7 @@ def case_empty_mesh_invalid(model, skip_before_version): return model.create_element_set().mesh +@pytest.mark.plotting @parametrize_with_cases("mesh", cases=".", glob="*_valid") def test_direction_plotter_valid_cases(model, mesh, load_model_from_tempfile): with load_model_from_tempfile() as model: @@ -75,11 +76,12 @@ def test_direction_plotter_valid_cases(model, mesh, load_model_from_tempfile): ) for idx, data in enumerate(components): - assert plotter.legend.GetEntryString(idx) == _replace_underscores_and_capitalize( + assert plotter.legend.GetEntryString(idx) == replace_underscores_and_capitalize( data.component_name ) +@pytest.mark.plotting @parametrize_with_cases("mesh", cases=".", glob="*_invalid") def test_direction_plotter_invalid_cases(model, mesh, load_model_from_tempfile): with load_model_from_tempfile() as model: From 19f59d5ed8d4345289307414c741227deda97323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:56:29 +0100 Subject: [PATCH 69/96] Definition of ply materials (Fabric, Stackup and Sub-Laminate) (#692) * add an example for material, stackup, fabric, and sub-laminate * fix pymechanical examples if run locally --- doc/create_doc_windows.ps1 | 5 +- .../sphx_glr_001-materials_thumb.png | Bin 0 -> 35939 bytes examples/modeling_features/001-materials.py | 188 ++++++++++++++++++ .../03-pymechanical-shell-workflow.py | 2 +- .../04-pymechanical-solid-workflow.py | 2 +- src/ansys/acp/core/extras/example_helpers.py | 29 ++- 6 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 doc/source/_static/gallery_thumbnails/sphx_glr_001-materials_thumb.png create mode 100644 examples/modeling_features/001-materials.py diff --git a/doc/create_doc_windows.ps1 b/doc/create_doc_windows.ps1 index 404e0570a7..00c3ed05ec 100644 --- a/doc/create_doc_windows.ps1 +++ b/doc/create_doc_windows.ps1 @@ -24,7 +24,6 @@ $Env:PYACP_DOC_SKIP_API=0 docker-compose -f ./docker-compose/docker-compose-extras.yaml up -d -cd doc -.\make.bat html -cd .. +.\doc\make.bat html + docker-compose -f ./docker-compose/docker-compose-extras.yaml down diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_001-materials_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_001-materials_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..6c2fa014dc1a9169255ba70b42e606b2cec625c1 GIT binary patch literal 35939 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 I==Z??1K3yM%K!iX literal 0 HcmV?d00001 diff --git a/examples/modeling_features/001-materials.py b/examples/modeling_features/001-materials.py new file mode 100644 index 0000000000..d57f5c937a --- /dev/null +++ b/examples/modeling_features/001-materials.py @@ -0,0 +1,188 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _materials_example: + +Materials +========= + +This example demonstrates how to create the different type of materials, import, +or export them. It only shows the PyACP part of the setup. For a complete composite analysis, +see :ref:`pymapdl_workflow_example`. + +ACP distinguishes between four types of material: + +- Raw **Material** that defines the mechanical properties of the material. +- **Fabric** is where a material can be associated with a set thickness. +- **Stackup** is used to combine fabrics into a non-crimp fabric, such as a [0 45 90] combination. +- **Sublaminate** is used to group fabrics and stackups for frequently used lay-ups. + +Fabrics, Stackups and Sublaminates can be used to create plies. It is recommended to look a the +Ansys help for more information on the different types of materials. +""" +import os + +# %% +# Import the standard library and third-party dependencies. +import pathlib +import tempfile + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import ACPWorkflow, FabricWithAngle, Lamina, PlyType, SymmetryType, launch_acp +from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.material_property_sets import ( + ConstantEngineeringConstants, + ConstantStrainLimits, + ConstantStressLimits, +) + +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_001-materials_thumb.png' + + +# %% +# Start ACP and load the model +# ---------------------------- +# %% +# Get the example file from the server. +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_DAT, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Define the input file and instantiate an ``ACPWorkflow`` instance. +workflow = ACPWorkflow.from_cdb_or_dat_file( + acp=acp, + cdb_or_dat_file_path=input_file, + local_working_directory=WORKING_DIR, +) + +model = workflow.model + +# %% +# Create a Material +# ----------------- +# %% +# Create property sets elastic constants, strain and stress limits. +engineering_constants_ud = ConstantEngineeringConstants.from_orthotropic_constants( + E1=5e10, E2=1e10, E3=1e10, nu12=0.28, nu13=0.28, nu23=0.3, G12=5e9, G23=4e9, G31=4e9 +) + +strain_limit_tension = 0.01 +strain_limit_compression = 0.008 +strain_limit_shear = 0.012 +strain_limits = ConstantStrainLimits.from_orthotropic_constants( + eXc=strain_limit_compression, + eYc=strain_limit_compression, + eZc=strain_limit_compression, + eXt=strain_limit_tension, + eYt=strain_limit_tension, + eZt=strain_limit_tension, + eSxy=strain_limit_shear, + eSyz=strain_limit_shear, + eSxz=strain_limit_shear, +) + +stress_limits = ConstantStressLimits.from_orthotropic_constants( + Xt=7.8e8, + Yt=3.1e7, + Zt=3.1e7, + Xc=-4.8e8, + Yc=-1e8, + Zc=-1e8, + Sxy=3.5e7, + Syz=2.5e7, + Sxz=3.5e7, +) + +# %% +# Create a uni-directional (UD) material +ud_material = model.create_material( + name="E-Glass UD", + ply_type=PlyType.REGULAR, + engineering_constants=engineering_constants_ud, + strain_limits=strain_limits, + stress_limits=stress_limits, +) + +# %% +# Create a Fabric +# --------------- +# +# Create a fabric with a thickness of 0.2 mmm. A material can be used for +# multiple fabrics. +ud_fabric_02mm = model.create_fabric( + name="E-Glass UD 0.2mm", material=ud_material, thickness=0.0002 +) +ud_fabric_03mm = model.create_fabric( + name="E-Glass UD 0.3mm", material=ud_material, thickness=0.0003 +) + +# %% +# Create a Stackup +# ---------------- +# Create a non-crimped fabric. In that case a biax. +biax_glass_ud = model.create_stackup( + name="Biax E-Glass UD [-45, 45]", + fabrics=( + FabricWithAngle(ud_fabric_02mm, -45), + FabricWithAngle(ud_fabric_02mm, 45), + ), +) + +# %% +# Create a Sub-Laminate +# --------------------- +# A Sublaminate is a group of fabrics and stackups which eases the modeling +# if the same sequence of materials is used multiple times. +# The final material sequence of this Sublaminate is +# [E-Glass -45°, E-Glass 45°, E-Glass 90°, E-Glass 45°, E-Glass -45°]. +sublaminate = model.create_sublaminate( + name="Sublaminate", + materials=( + Lamina(biax_glass_ud, 0), + Lamina(ud_fabric_02mm, 90), + ), + symmetry=SymmetryType.ODD_SYMMETRY, +) + +# %% +# Import and Export Materials +# --------------------------- +# Materials can be imported and exported from and to external sources. +# By default, materials are loaded from the CDB file when the model is loaded. +# An alternative is to load materials from an Engineering Data +# file via :meth:`.Model.import_materials`. +engd_file_path = get_example_file(ExampleKeys.MATERIALS_XML, WORKING_DIR) +remote_engd_file_path = acp.upload_file(engd_file_path) +model.import_materials(matml_path=remote_engd_file_path) + +# %% +# Some workflows require the materials to be exported to an XML file. +engd_file_name = "exported_materials.xml" +model.export_materials(path=engd_file_name) +acp.download_file(engd_file_name, os.path.join(WORKING_DIR, engd_file_name)) diff --git a/examples/workflows/03-pymechanical-shell-workflow.py b/examples/workflows/03-pymechanical-shell-workflow.py index 27df1da012..b779645ebe 100644 --- a/examples/workflows/03-pymechanical-shell-workflow.py +++ b/examples/workflows/03-pymechanical-shell-workflow.py @@ -306,7 +306,7 @@ definition=working_dir_path / composite_definitions_h5 ), }, - engineering_data=working_dir_path / matml_file, + engineering_data=working_dir_path / matml_out, ), server=dpf, ) diff --git a/examples/workflows/04-pymechanical-solid-workflow.py b/examples/workflows/04-pymechanical-solid-workflow.py index 83234b356d..47b20f762d 100644 --- a/examples/workflows/04-pymechanical-solid-workflow.py +++ b/examples/workflows/04-pymechanical-solid-workflow.py @@ -367,7 +367,7 @@ definition=working_dir_path / solid_model_composite_definitions_h5 ), }, - engineering_data=working_dir_path / matml_file, + engineering_data=working_dir_path / matml_out, ), server=dpf, ) diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index 645bd0ef6b..eed624ae3f 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -25,10 +25,12 @@ These utilities can download the input files used in the PyACP examples. """ - import dataclasses from enum import Enum, auto import pathlib +import shutil +import sys +import urllib.parse import urllib.request __all__ = ["ExampleKeys", "get_example_file"] @@ -41,6 +43,9 @@ _EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" +# _EXAMPLE_REPO = "D:\\ANSYSDev\\pyansys-example-data\\pyacp\\" + + @dataclasses.dataclass class _ExampleLocation: directory: str @@ -60,6 +65,7 @@ class ExampleKeys(Enum): MINIMAL_FLAT_PLATE = auto() OPTIMIZATION_EXAMPLE_DAT = auto() CLASS40_AGDB = auto() + MATERIALS_XML = auto() EXAMPLE_FILES: dict[ExampleKeys, _ExampleLocation] = { @@ -91,6 +97,7 @@ class ExampleKeys(Enum): directory="optimization_example", filename="optimization_model.dat" ), ExampleKeys.CLASS40_AGDB: _ExampleLocation(directory="class40", filename="class40.agdb"), + ExampleKeys.MATERIALS_XML: _ExampleLocation(directory="materials", filename="materials.engd"), } @@ -109,14 +116,26 @@ def get_example_file(example_key: ExampleKeys, working_directory: pathlib.Path) return working_directory / example_location.filename -def _get_file_url(example_location: _ExampleLocation) -> str: - return _EXAMPLE_REPO + "/".join([example_location.directory, example_location.filename]) +def _is_url(path_url: str) -> bool: # pragma: no cover + return urllib.parse.urlparse(path_url).scheme in ["http", "https"] + + +def _get_file_url(example_location: _ExampleLocation) -> str: # pragma: no cover + if sys.platform == "win32" and not _is_url(_EXAMPLE_REPO): + return _EXAMPLE_REPO + "\\".join([example_location.directory, example_location.filename]) + else: + return _EXAMPLE_REPO + "/".join([example_location.directory, example_location.filename]) -def _download_file(example_location: _ExampleLocation, local_path: pathlib.Path) -> None: +def _download_file( + example_location: _ExampleLocation, local_path: pathlib.Path +) -> None: # pragma: no cover file_url = _get_file_url(example_location) # The URL is hard-coded to start with the example repository URL, so it is safe to use - urllib.request.urlretrieve(file_url, local_path) # nosec: B310 + if _is_url(file_url): + urllib.request.urlretrieve(file_url, local_path) # nosec: B310 + else: + shutil.copyfile(file_url, local_path) def _run_analysis(workflow: "ACPWorkflow") -> None: From 44ce704033805fe3f520e77f3ea5ba0f7773a493 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 25 Nov 2024 08:10:47 +0100 Subject: [PATCH 70/96] Update the advanced PyMAPDL example (#690) Update the advanced PyMAPDL (class40) example: - Download the input file from the ``example_data`` repository instead of relying on a file in the PyACP repository - Use temporary local working directories, to avoid polluting the local directory. - Test that the example runs with a local install, and remove the note about Docker containers. Note that this is not checked continuously in CI. - Move the ``class40.cdb`` input file to ``tests/data``, since it is now used only in the benchmark test. --- .../workflows/02-advanced-pymapdl-workflow.py | 85 ++++++++++--------- src/ansys/acp/core/extras/example_helpers.py | 2 + tests/benchmarks/test_class40.py | 17 ++-- .../data/class40 => tests/data}/class40.cdb | 0 4 files changed, 53 insertions(+), 51 deletions(-) rename {examples/data/class40 => tests/data}/class40.cdb (100%) diff --git a/examples/workflows/02-advanced-pymapdl-workflow.py b/examples/workflows/02-advanced-pymapdl-workflow.py index f0c81ca99b..a25a7c2a73 100644 --- a/examples/workflows/02-advanced-pymapdl-workflow.py +++ b/examples/workflows/02-advanced-pymapdl-workflow.py @@ -35,8 +35,6 @@ PyDPF Composites for postprocessing. The additional input files (``material.xml`` and ``ACPCompositeDefinitions.h5``) can also be stored with PyACP and passed to PyDPF Composites. -The MAPDL and DPF services are run in Docker containers that share a volume (working -directory). """ # %% @@ -45,7 +43,6 @@ # %% # Import the standard library and third-party dependencies. -import os import pathlib import tempfile @@ -63,24 +60,26 @@ acp = pyacp.launch_acp() # %% +# Get example input files +# ----------------------- # -# Load mesh and materials from CDB file -# ------------------------------------- +# Create a temporary working directory, and download the example input files +# to this directory. + +working_dir = tempfile.TemporaryDirectory() +working_dir_path = pathlib.Path(working_dir.name) +input_file = pyacp.extras.example_helpers.get_example_file( + pyacp.extras.example_helpers.ExampleKeys.CLASS40_CDB, working_dir_path +) # %% -# Define the directory in which the input files are stored. -try: - EXAMPLES_DIR = pathlib.Path(os.environ["REPO_ROOT"]) / "examples" -except KeyError: - EXAMPLES_DIR = pathlib.Path(__file__).parent -EXAMPLE_DATA_DIR = EXAMPLES_DIR / "data" / "class40" +# +# Load mesh and materials from CDB file +# ------------------------------------- # %% -# Send ``class40.cdb`` to the server. -CDB_FILENAME = "class40.cdb" -local_file_path = str(EXAMPLE_DATA_DIR / CDB_FILENAME) -print(local_file_path) -cdb_file_path = acp.upload_file(local_path=local_file_path) +# Send the input file to the ACP server. +cdb_file_path = acp.upload_file(local_path=input_file) # %% # Load the CDB file into PyACP and set the unit system. @@ -286,35 +285,39 @@ def add_ply(mg, name, ply_material, angle, oss): # Write out ACP Model # ------------------- -ACPH5_FILE = "class40.acph5" -CDB_FILENAME_OUT = "class40_analysis_model.cdb" -COMPOSITE_DEFINITIONS_H5 = "ACPCompositeDefinitions.h5" -MATML_FILE = "materials.xml" +acph5_filename = "class40.acph5" +cdb_filename_out = "class40_analysis_model.cdb" +composite_definition_h5_filename = "ACPCompositeDefinitions.h5" +matml_filename = "materials.xml" + +if acp.is_remote: + export_path = pathlib.PurePosixPath(".") +else: + export_path = working_dir_path # type: ignore # %% # Update and save the ACP model. model.update() -model.save(ACPH5_FILE, save_cache=True) +model.save(export_path / acph5_filename, save_cache=True) # %% # Save the model as a CDB file for solving with PyMAPDL. -model.export_analysis_model(CDB_FILENAME_OUT) +model.export_analysis_model(export_path / cdb_filename_out) # Export the shell lay-up and material file for PyDPF Composites. -model.export_shell_composite_definitions(COMPOSITE_DEFINITIONS_H5) -model.export_materials(MATML_FILE) +model.export_shell_composite_definitions(export_path / composite_definition_h5_filename) +model.export_materials(export_path / matml_filename) # %% # Download files from the ACP server to a local directory. -tmp_dir = tempfile.TemporaryDirectory() -WORKING_DIR = pathlib.Path(tmp_dir.name) -cdb_file_local_path = pathlib.Path(WORKING_DIR) / CDB_FILENAME_OUT -matml_file_local_path = pathlib.Path(WORKING_DIR) / MATML_FILE -composite_definitions_local_path = pathlib.Path(WORKING_DIR) / COMPOSITE_DEFINITIONS_H5 -acp.download_file(remote_filename=CDB_FILENAME_OUT, local_path=str(cdb_file_local_path)) -acp.download_file(remote_filename=MATML_FILE, local_path=str(matml_file_local_path)) -acp.download_file( - remote_filename=COMPOSITE_DEFINITIONS_H5, local_path=str(composite_definitions_local_path) -) +for filename in [ + acph5_filename, + cdb_filename_out, + composite_definition_h5_filename, + matml_filename, +]: + acp.download_file( + remote_filename=export_path / filename, local_path=working_dir_path / filename + ) # %% # Solve with PyMAPDL @@ -326,9 +329,10 @@ def add_ply(mg, name, ply_material, angle, oss): mapdl = launch_mapdl() mapdl.clear() + # %% # Load the CDB file into PyMAPDL. -mapdl.input(str(cdb_file_local_path)) +mapdl.input(str(working_dir_path / cdb_filename_out)) # %% # Solve the model. @@ -344,8 +348,7 @@ def add_ply(mg, name, ply_material, angle, oss): # Download the RST file for further postprocessing. rstfile_name = f"{mapdl.jobname}.rst" -rst_file_local_path = pathlib.Path(tmp_dir.name) / rstfile_name -mapdl.download(rstfile_name, tmp_dir.name) +mapdl.download(rstfile_name, working_dir_path) # %% # Postprocessing with PyDPF Composites @@ -389,11 +392,13 @@ def add_ply(mg, name, ply_material, angle, oss): # Create the composite model and configure its input. composite_model = CompositeModel( composite_files=ContinuousFiberCompositesFiles( - rst=rst_file_local_path, + rst=working_dir_path / rstfile_name, composite={ - "shell": CompositeDefinitionFiles(definition=composite_definitions_local_path), + "shell": CompositeDefinitionFiles( + definition=working_dir_path / composite_definition_h5_filename + ), }, - engineering_data=matml_file_local_path, + engineering_data=working_dir_path / matml_filename, ), default_unit_system=unit_systems.solver_nmm, server=dpf_server, diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index eed624ae3f..934e91a5a9 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -65,6 +65,7 @@ class ExampleKeys(Enum): MINIMAL_FLAT_PLATE = auto() OPTIMIZATION_EXAMPLE_DAT = auto() CLASS40_AGDB = auto() + CLASS40_CDB = auto() MATERIALS_XML = auto() @@ -97,6 +98,7 @@ class ExampleKeys(Enum): directory="optimization_example", filename="optimization_model.dat" ), ExampleKeys.CLASS40_AGDB: _ExampleLocation(directory="class40", filename="class40.agdb"), + ExampleKeys.CLASS40_CDB: _ExampleLocation(directory="class40", filename="class40.cdb"), ExampleKeys.MATERIALS_XML: _ExampleLocation(directory="materials", filename="materials.engd"), } diff --git a/tests/benchmarks/test_class40.py b/tests/benchmarks/test_class40.py index 504039b9d3..90e4b36b77 100644 --- a/tests/benchmarks/test_class40.py +++ b/tests/benchmarks/test_class40.py @@ -24,24 +24,19 @@ import ansys.acp.core as pyacp -from ..conftest import SOURCE_ROOT_DIR - -@pytest.mark.benchmark( - min_rounds=1, -) -def test_class40(benchmark, acp_instance): +@pytest.mark.benchmark(min_rounds=1) +def test_class40(benchmark, acp_instance, model_data_dir): """Benchmark for creating a composite lay-up for the Class40 model.""" - benchmark(create_class40, acp_instance) + class40_file = model_data_dir / "class40.cdb" + benchmark(create_class40, acp_instance, class40_file) -def create_class40(pyacp_client): +def create_class40(pyacp_client, cdb_file): """ Create a composite lay-up for the Class40 model. """ - examples_data_dir = SOURCE_ROOT_DIR / "examples" / "data" / "class40" - - cdb_file_path = pyacp_client.upload_file(local_path=str(examples_data_dir / "class40.cdb")) + cdb_file_path = pyacp_client.upload_file(local_path=cdb_file) model = pyacp_client.import_model( path=cdb_file_path, format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA diff --git a/examples/data/class40/class40.cdb b/tests/data/class40.cdb similarity index 100% rename from examples/data/class40/class40.cdb rename to tests/data/class40.cdb From 91b0a48aa4d31b7a4e70853e755998b5783907a6 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 25 Nov 2024 09:04:42 +0100 Subject: [PATCH 71/96] CI: use only the plotting extra in the vulnerability check action (#699) Switch from the `all` extra to `plotting` in the `check-vulnerabilities` action. We previously used `all` since poetry otherwise removes dependencies needed by the action itself, but this has been resolved in https://github.com/ansys/actions/pull/650 --- .github/workflows/ci_cd.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 0d3faf178c..459fff9070 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -116,9 +116,7 @@ jobs: token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} python-package-name: 'ansys-acp-core' dev-mode: ${{ github.ref != 'refs/heads/main' }} - # In principle the 'plotting' extra would be sufficient, but poetry - # then removes 'setuptools' which is needed by the action. - extra-targets: "all" + extra-targets: "plotting" testing-minimum-deps: name: Testing with minimum dependencies From f5e3c842341618a2e6e20ec1bc2535824fac3e44 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 25 Nov 2024 09:50:38 +0100 Subject: [PATCH 72/96] Workflow: allow loading Mechanical H5, update 'create input file' doc (#689) Allow loading the Mechanical to ACP H5 transfer file in the workflow class, by adding a new classmethod 'from_mechanical_h5_file'. Update the 'create input file' how-to in the documentation to reflect the fact that Mechanical H5 file input is also supported. Closes #681 --- .../_static/gallery_thumbnails/README.md | 2 + doc/source/examples/index.rst | 2 + doc/source/index.rst | 2 +- .../user_guide/howto/create_input_file.rst | 51 +++++++++++++++---- src/ansys/acp/core/_workflow.py | 30 +++++++++++ tests/unittests/test_workflow.py | 33 ++++++++++++ 6 files changed, 108 insertions(+), 12 deletions(-) diff --git a/doc/source/_static/gallery_thumbnails/README.md b/doc/source/_static/gallery_thumbnails/README.md index ef35fe7775..20fcac58ca 100644 --- a/doc/source/_static/gallery_thumbnails/README.md +++ b/doc/source/_static/gallery_thumbnails/README.md @@ -1,3 +1,5 @@ +.. vale off + This directory contains thumbnails for the gallery examples which are not built in CI. The thumbnails are used in the gallery index page. diff --git a/doc/source/examples/index.rst b/doc/source/examples/index.rst index 3c63d7d056..5cc4bd8c3e 100644 --- a/doc/source/examples/index.rst +++ b/doc/source/examples/index.rst @@ -13,6 +13,8 @@ These examples show how to use PyACP for defining composite layups. :start-line: 2 +.. _workflow_examples: + Workflow examples ================= diff --git a/doc/source/index.rst b/doc/source/index.rst index d7ec9df3fb..680c30e994 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -84,7 +84,7 @@ Limitations * It only works on Windows, with a remote (not embedded) PyMechanical session. * Only one ACP shell or solid model is supported at a time. * Named selections defined in ACP are not transferred to PyMechanical. - * The ``ansys.acp.core.mechanical_integration_helpers`` module will be + * The ``ansys.acp.core.mechanical_integration_helpers`` module may be changed or removed in future versions, when the corresponding features are available in PyMechanical directly. diff --git a/doc/source/user_guide/howto/create_input_file.rst b/doc/source/user_guide/howto/create_input_file.rst index e0a9ae67c6..da40fedff7 100644 --- a/doc/source/user_guide/howto/create_input_file.rst +++ b/doc/source/user_guide/howto/create_input_file.rst @@ -1,20 +1,39 @@ .. _input_file_for_pyacp: Create input file for PyACP ---------------------------- +=========================== To start working with PyACP, an input file that contains the mesh is required. PyACP supports reading -the mesh from CDB and DAT files with the :meth:`.ACPWorkflow.from_cdb_or_dat_file()` method. PyACP reads the mesh, including any coordinate systems, element sets, -edge sets, and materials from the input file. Once the layup has been created with PyACP, you can export its CDB file with the :meth:`.ACPWorkflow.get_local_cdb_file` method. This file -contains all the data from the initial input file along with the layup information and -materials added by PyACP. An attempt is made to preserve the original input file as much as possible. -This includes the original mesh, materials, and boundary conditions. Therefore, you may directly use the exported CDB file -for an analysis through PyMAPDL. For more information, see :ref:`pymapdl_workflow_example`. +the mesh from: -.. _input_file_from_mechanical: +#. CDB or DAT files, using either the :meth:`.ACPWorkflow.from_cdb_or_dat_file()` method, or the :meth:`.ACPInstance.import_model()` method with the ``"ansys::cdb"`` or ``"ansys:dat"`` format. +#. HDF5 transfer files generated by Mechanical, using the :meth:`.ACPWorkflow.from_mechanical_h5_file()` method or the :meth:`.ACPInstance.import_model()` method with the ``"ansys::h5"`` format. -Create an input file with Ansys Mechanical -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +PyACP reads the mesh, including any coordinate systems, element sets, and edge sets +from the input file. In the case of CDB or DAT files, PyACP also reads the materials +from the input file. +Once the layup has been created with PyACP, you can export the model for downstream analysis. + +.. important:: + + The :meth:`.Model.export_analysis_model` method to *export* a CDB file from PyACP is only + available if the input was read from a CDB or DAT file. + If the input was read from a Mechanical HDF5 transfer file, you can export the model + either to the HDF5 Composite CAE format, or to transfer formats to PyMechanical. + See the :ref:`workflow examples ` for more information. + +The following sections describe how to create the input file in CDB or Mechanical HDF5 format. + +Create a CDB input file +----------------------- + +.. _cdb_file_from_mechanical: + +Create a CDB file with Ansys Mechanical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the Mechanical GUI +''''''''''''''''''''' One way to create an input file for PyACP is to create a static structural setup and export the solver input file. To do this, follow these steps: @@ -35,6 +54,11 @@ For a complete example, see :ref:`pymapdl_workflow_example`. The imported model always contains the dummy material named ``1`` that was assigned to the geometry. +With scripting +'''''''''''''' + +You can also create a CDB file from Mechanical or PyMechanical with scripting, using the ``WriteInputFile`` method of the analysis object. See :ref:`pymechanical_to_cdb_example` for a complete example. + Create an input file with Ansys Mechanical APDL ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -57,5 +81,10 @@ Notes on material handling Materials present in the input file (\*.cdb or \*.dat) are read into PyACP. The following rules apply: -* If the material has defined a UVID, then the material is imported as locked. This means the material cannot be edited in PyACP. If the input file was created with Ansys Mechanical (see :ref:`input_file_from_mechanical`), this is always the case. In Mechanical APDL, you can define a UVID with the ``MP,UVID`` or ``MPDATAT,UNBL,16,UVID`` command. +* If the material has defined a UVID, then the material is imported as locked. This means the material cannot be edited in PyACP. If the input file was created with Ansys Mechanical (see :ref:`cdb_file_from_mechanical`), this is always the case. In Mechanical APDL, you can define a UVID with the ``MP,UVID`` or ``MPDATAT,UNBL,16,UVID`` command. * If the material has no UVID, then the material is copied on import. Only the copied material appears in PyACP. The original material is not changed and appears unmodified in the output file. + +Create a Mechanical HDF5 transfer file (experimental) +----------------------------------------------------- + +The Mechanical to ACP HDF5 transfer file can be created using the :func:`.export_mesh_for_acp` helper function. See :ref:`pymechanical_shell_example` or :ref:`pymechanical_solid_example` for complete examples. diff --git a/src/ansys/acp/core/_workflow.py b/src/ansys/acp/core/_workflow.py index 3b80626c8d..63a0c06331 100644 --- a/src/ansys/acp/core/_workflow.py +++ b/src/ansys/acp/core/_workflow.py @@ -209,6 +209,36 @@ def from_acph5_file( file_format="acp:h5", ) + @classmethod + def from_mechanical_h5_file( + cls, + acp: ACPInstance[ServerProtocol], + h5_file_path: PATH, + local_working_directory: PATH | None = None, + ) -> "ACPWorkflow": + """Instantiate an ACP Workflow from a Mechanical HDF5 file. + + Create an ACP Workflow from the Mechanical to ACP HDF5 transfer + file. This file can be created using the :func:`.export_mesh_for_acp` + function. + + Parameters + ---------- + acp: + The ACP Client. + h5_file_path: + The path to the HDF5 file. + local_working_directory: + The local working directory. If None, a temporary directory will be created. + """ + + return cls( + acp=acp, + local_file_path=h5_file_path, + local_working_directory=local_working_directory, + file_format="ansys:h5", + ) + @classmethod def from_cdb_or_dat_file( cls, diff --git a/tests/unittests/test_workflow.py b/tests/unittests/test_workflow.py index 15bb720fc4..b0c53e04b0 100644 --- a/tests/unittests/test_workflow.py +++ b/tests/unittests/test_workflow.py @@ -65,6 +65,39 @@ def test_workflow(acp_instance, model_data_dir, explict_temp_dir): assert composite_definitions.is_file() +@pytest.mark.parametrize("explict_temp_dir", [None, tempfile.TemporaryDirectory()]) +def test_workflow_from_mechanical_h5(acp_instance, model_data_dir, explict_temp_dir): + """Test that workflow can be initialized from a Mechanical HDF5 transfer file.""" + input_file_path = model_data_dir / "ACP-Pre.h5" + + if explict_temp_dir is not None: + working_dir = pathlib.Path(explict_temp_dir.name) + else: + working_dir = None + + workflow = ACPWorkflow.from_mechanical_h5_file( + acp=acp_instance, + h5_file_path=input_file_path, + local_working_directory=working_dir, + ) + workflow.model.update() + + with pytest.raises(RuntimeError): + workflow.get_local_cdb_file() + + acph5_path = workflow.get_local_acph5_file() + assert acph5_path == workflow.working_directory.path / f"{workflow.model.name}.acph5" + assert acph5_path.is_file() + + materials_path = workflow.get_local_materials_file() + assert materials_path == workflow.working_directory.path / "materials.xml" + assert materials_path.is_file() + + composite_definitions = workflow.get_local_composite_definitions_file() + assert composite_definitions == workflow.working_directory.path / "ACPCompositeDefinitions.h5" + assert composite_definitions.is_file() + + def test_reload_cad_geometry(acp_instance, model_data_dir, load_cad_geometry): input_file_path = model_data_dir / "minimal_model_2.cdb" From 8cf45e083d3e9b8be5a78882d60ce45c018c9cc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 09:09:45 +0000 Subject: [PATCH 73/96] Bump the dependencies group with 2 updates (#698) * Bump the dependencies group with 2 updates Bumps the dependencies group with 2 updates: [ansys-sphinx-theme](https://github.com/ansys/ansys-sphinx-theme) and [hypothesis](https://github.com/HypothesisWorks/hypothesis). Updates `ansys-sphinx-theme` from 1.2.1 to 1.2.2 - [Release notes](https://github.com/ansys/ansys-sphinx-theme/releases) - [Commits](https://github.com/ansys/ansys-sphinx-theme/compare/v1.2.1...v1.2.2) Updates `hypothesis` from 6.119.3 to 6.119.4 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.119.3...hypothesis-python-6.119.4) --- updated-dependencies: - dependency-name: ansys-sphinx-theme dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] * Ignore vale on internal README --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dominik Gresch --- doc/source/_static/gallery_thumbnails/README.md | 2 +- poetry.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/source/_static/gallery_thumbnails/README.md b/doc/source/_static/gallery_thumbnails/README.md index 20fcac58ca..ce898610c3 100644 --- a/doc/source/_static/gallery_thumbnails/README.md +++ b/doc/source/_static/gallery_thumbnails/README.md @@ -1,4 +1,4 @@ -.. vale off + This directory contains thumbnails for the gallery examples which are not built in CI. The thumbnails are used in the gallery index page. diff --git a/poetry.lock b/poetry.lock index 7bc6db67ee..a49df5874e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -475,13 +475,13 @@ clr-loader = ">=0.2.5,<0.3.0" [[package]] name = "ansys-sphinx-theme" -version = "1.2.1" +version = "1.2.2" description = "A theme devised by ANSYS, Inc. for Sphinx documentation." optional = false python-versions = "<4,>=3.10" files = [ - {file = "ansys_sphinx_theme-1.2.1-py3-none-any.whl", hash = "sha256:b133ce7b94ef50c6043ee088fe32ed222774eb0af178fc5c07c960941c027360"}, - {file = "ansys_sphinx_theme-1.2.1.tar.gz", hash = "sha256:9eacd4a242ca1747b61e930b6e134f4659b198ac61bc4290ccd0c461cbae5549"}, + {file = "ansys_sphinx_theme-1.2.2-py3-none-any.whl", hash = "sha256:c1ea0dc37ce4263946514b4d11d59bca1ae8ddf3a79081be1a415146a4df9f90"}, + {file = "ansys_sphinx_theme-1.2.2.tar.gz", hash = "sha256:2e640ee9a6466bd8de1de007037d29e3f0205c2e7ff98b1999fa2e228fa77983"}, ] [package.dependencies] @@ -1862,13 +1862,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.119.3" +version = "6.119.4" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.119.3-py3-none-any.whl", hash = "sha256:dff13689c4ceb0d84d92e0309fca3ccc1b547ac30552037c712a9080eb75cd05"}, - {file = "hypothesis-6.119.3.tar.gz", hash = "sha256:1403676d95bc9f118a30ce2c97fcbdd28dd99f3a1ffe3456970d98a56b370f36"}, + {file = "hypothesis-6.119.4-py3-none-any.whl", hash = "sha256:333958da7855048850c3d2b6a929d44a3c89ca9eafcfddcacc3570140915eba5"}, + {file = "hypothesis-6.119.4.tar.gz", hash = "sha256:1a7d12709c0e96c1d85aca76d1594b34b5958623e00511592eba674acd4f3392"}, ] [package.dependencies] From 88f7bc19c3e8fa6307e0c00fc1df6a0fc4f0d8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:47:58 +0100 Subject: [PATCH 74/96] add example for solid model (#695) * add solid model example * add plotting examples for a solid model --- .../user_guide/howto/visualize_model.rst | 6 + examples/modeling_features/020-solid_model.py | 228 ++++++++++++++++++ src/ansys/acp/core/extras/__init__.py | 4 +- src/ansys/acp/core/extras/example_helpers.py | 8 + tests/unittests/test_modeling_ply.py | 10 + tests/unittests/test_stackup.py | 8 + tests/unittests/test_sublaminate.py | 14 ++ tests/unittests/test_virtual_geometry.py | 10 + 8 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 examples/modeling_features/020-solid_model.py diff --git a/doc/source/user_guide/howto/visualize_model.rst b/doc/source/user_guide/howto/visualize_model.rst index fd16089726..b3d747878c 100644 --- a/doc/source/user_guide/howto/visualize_model.rst +++ b/doc/source/user_guide/howto/visualize_model.rst @@ -176,6 +176,12 @@ Visualize model >>> acp.stop(timeout=0) + Showing solid model + ~~~~~~~~~~~~~~~~~~~ + + The visualization of a solid mesh and its elemental data is shown in the example :ref:`solid_model_example`. + + {% else %} .. note:: diff --git a/examples/modeling_features/020-solid_model.py b/examples/modeling_features/020-solid_model.py new file mode 100644 index 0000000000..06a0cdd808 --- /dev/null +++ b/examples/modeling_features/020-solid_model.py @@ -0,0 +1,228 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _solid_model_example: + +Solid Model +=========== + +This example shows how to create and shape a solid model. + +The solid model implements an extrusion algorithm which creates +a layered solid mesh based on the shell mesh and layup definition. +This solid mesh can be further processed by :class:`.ExtrusionGuide`, +:class:`.SnapToGeometry`, and :class:`.CutOffGeometry`. +""" +# %% +# Import the standard library and third-party dependencies. +import pathlib +import tempfile + +import pyvista + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import ( + ACPWorkflow, + CADGeometry, + CutOffGeometryOrientationType, + EdgeSetType, + ExtrusionGuideType, + SnapToGeometryOrientationType, + VirtualGeometry, + get_directions_plotter, + launch_acp, +) +from ansys.acp.core.extras import ExampleKeys, get_example_file + +# sphinx_gallery_thumbnail_number = 4 + + +# %% +# Load a minimal model +# ---------------------------- +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +input_file = get_example_file(ExampleKeys.MINIMAL_FLAT_PLATE, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Define the input file and instantiate an ``ACPWorkflow`` instance. +workflow = ACPWorkflow.from_acph5_file( + acp=acp, + acph5_file_path=input_file, + local_working_directory=WORKING_DIR, +) + +model = workflow.model + +# %% +# Create a simple layup +# --------------------- +# %% +# Add more layers to the modeling ply so that it is easier to see the +# effects of the selection rules. +modeling_ply = model.modeling_groups["modeling_group"].modeling_plies["ply"] +modeling_ply.number_of_layers = 3 + +# %% +# Create an initial solid model +# ----------------------------- +# +# By default, the layup is extruded along the normal direction of the shell mesh. +solid_model = model.create_solid_model( + name="Solid Model", + element_sets=[model.element_sets["All_Elements"]], +) + +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + + +def create_virtual_geometry_from_file( + example_key: ExampleKeys, +) -> tuple[CADGeometry, VirtualGeometry]: + """Create a CAD geometry and virtual geometry.""" + geometry_file = get_example_file(example_key, WORKING_DIR) + geometry_obj = workflow.add_cad_geometry_from_local_file(geometry_file) + workflow.model.update() + virtual_geometry = model.create_virtual_geometry( + name="thickness_virtual_geometry", cad_components=geometry_obj.root_shapes.values() + ) + return geometry_obj, virtual_geometry + + +def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float = 0.1): + """Plot solid model and geometry.""" + plotter = pyvista.Plotter() + geom_mesh = cad_geometry.visualization_mesh.to_pyvista() + plotter.add_mesh(geom_mesh, color="green", opacity=cad_geom_opacity) + edges = geom_mesh.extract_feature_edges() + plotter.add_mesh(edges, color="white", line_width=4) + plotter.add_mesh(edges, color="black", line_width=2) + plotter.add_mesh(workflow.model.solid_mesh.to_pyvista(), show_edges=True) + plotter.show() + + +# %% +# Snap the top to a geometry +# -------------------------- +# +# The :class:`.SnapToGeometry` allows to shape the bottom or top of the solid model. +# First, import the geometry and then add the snap-to feature to the solid model. +snap_to_geom, snap_to_virtual_geom = create_virtual_geometry_from_file(ExampleKeys.SNAP_TO_GEOMETRY) +solid_model.create_snap_to_geometry( + name="Snap-to Geometry", + cad_geometry=snap_to_virtual_geom, + orientation_type=SnapToGeometryOrientationType.TOP, + oriented_selection_set=model.oriented_selection_sets["oss"], +) + +model.update() +plot_model_with_geometry(snap_to_geom, 0.5) + +# %% +# Shape the walls +# --------------- +# +# The :class:`.ExtrusionGuide` is used to shape the side walls of the solid model. +# The feature can be defined by a direction as shown here or through a geometry. +edge_set = model.create_edge_set( + name="Edge Set", + edge_set_type=EdgeSetType.BY_REFERENCE, + element_set=model.element_sets["All_Elements"], + limit_angle=30, + origin=(0.05, 0, 0), +) +solid_model.create_extrusion_guide( + name="Extrusion Guide", + edge_set=edge_set, + extrusion_guide_type=ExtrusionGuideType.BY_DIRECTION, + direction=(-0.5, 1, 0), + radius=0.005, + depth=0.6, +) +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# Cut-off an edge +# --------------- +# +# The :class:`.CutOffGeometry` is used to crop elements from the solid model. +cutoff_cad_geom, cutoff_virtual_geom = create_virtual_geometry_from_file( + ExampleKeys.CUT_OFF_GEOMETRY_SOLID_MODEL +) +solid_model.create_cut_off_geometry( + name="Cut-off Geometry", + cad_geometry=cutoff_virtual_geom, + orientation_type=CutOffGeometryOrientationType.UP, +) + +model.update() +plot_model_with_geometry(cutoff_cad_geom) + + +# %% +# Plot results on the solid mesh +# ------------------------------ +# +# The plotting capabilities also support the visualization of ply-wise results, +# such as directions or thicknesses as shown here. + +# %% +# Get the analysis ply of interest +ap = ( + model.modeling_groups["modeling_group"] + .modeling_plies["ply"] + .production_plies["ProductionPly.2"] + .analysis_plies["P2L1__ply"] +) + +# %% +# Plot fiber directions +# ~~~~~~~~~~~~~~~~~~~~~ +direction_plotter = get_directions_plotter( + model=model, + mesh=ap.solid_mesh, + components=[ + ap.elemental_data.fiber_direction, + ], + length_factor=10.0, + culling_factor=10, +) +direction_plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=True) +direction_plotter.show() + +# %% +# Plot thicknesses +# ~~~~~~~~~~~~~~~~ +thickness_data = ap.elemental_data.thickness +thickness_pyvista_mesh = thickness_data.get_pyvista_mesh(mesh=ap.solid_mesh) # type: ignore +plotter = pyvista.Plotter() +plotter.add_mesh(thickness_pyvista_mesh) +plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=True) +plotter.show() diff --git a/src/ansys/acp/core/extras/__init__.py b/src/ansys/acp/core/extras/__init__.py index caecb4c948..02b7305b3e 100644 --- a/src/ansys/acp/core/extras/__init__.py +++ b/src/ansys/acp/core/extras/__init__.py @@ -21,9 +21,9 @@ # SOFTWARE. """Extras of the Ansys Composites PrepPost module.""" -from ansys.acp.core.extras.example_helpers import ExampleKeys, get_example_file # pragma: no cover +from ansys.acp.core.extras.example_helpers import ExampleKeys, get_example_file -__all__ = [ # pragma: no cover +__all__ = [ "ExampleKeys", "get_example_file", ] diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index 934e91a5a9..3c121ebe46 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -67,6 +67,8 @@ class ExampleKeys(Enum): CLASS40_AGDB = auto() CLASS40_CDB = auto() MATERIALS_XML = auto() + SNAP_TO_GEOMETRY = auto() + CUT_OFF_GEOMETRY_SOLID_MODEL = auto() EXAMPLE_FILES: dict[ExampleKeys, _ExampleLocation] = { @@ -100,6 +102,12 @@ class ExampleKeys(Enum): ExampleKeys.CLASS40_AGDB: _ExampleLocation(directory="class40", filename="class40.agdb"), ExampleKeys.CLASS40_CDB: _ExampleLocation(directory="class40", filename="class40.cdb"), ExampleKeys.MATERIALS_XML: _ExampleLocation(directory="materials", filename="materials.engd"), + ExampleKeys.SNAP_TO_GEOMETRY: _ExampleLocation( + directory="geometries", filename="snap_to_geometry.stp" + ), + ExampleKeys.CUT_OFF_GEOMETRY_SOLID_MODEL: _ExampleLocation( + directory="geometries", filename="cut_off_geometry_solid_model.stp" + ), } diff --git a/tests/unittests/test_modeling_ply.py b/tests/unittests/test_modeling_ply.py index fdeaca27d1..b676db551a 100644 --- a/tests/unittests/test_modeling_ply.py +++ b/tests/unittests/test_modeling_ply.py @@ -455,3 +455,13 @@ def test_linked_cutoff_selection_rule_operation_type(operation_type): operation_type=operation_type, ) assert "INTERSECT" in str(exc.value) + + +def test_taper_edge(parent_model): + edge_1 = parent_model.create_edge_set() + taper_edge = TaperEdge(edge_set=edge_1, angle=1, offset=2) + assert taper_edge != TaperEdge(edge_set=parent_model.create_edge_set(), angle=1, offset=2) + assert taper_edge != TaperEdge(edge_set=edge_1, angle=2, offset=2) + assert taper_edge != TaperEdge(edge_set=edge_1, angle=1, offset=3) + assert taper_edge == TaperEdge(edge_set=edge_1, angle=1, offset=2) + print(taper_edge) diff --git a/tests/unittests/test_stackup.py b/tests/unittests/test_stackup.py index eec4c452b0..036b89d391 100644 --- a/tests/unittests/test_stackup.py +++ b/tests/unittests/test_stackup.py @@ -188,3 +188,11 @@ def test_add_fabric(parent_object): stackup.add_fabric(fabric2, angle=45.0) assert stackup.fabrics[-1].fabric == fabric2 assert stackup.fabrics[-1].angle == 45.0 + + +def test_fabric_wit_angle(parent_object): + fabric1 = parent_object.create_fabric() + fabric_with_angle = FabricWithAngle(fabric=fabric1, angle=45.0) + assert fabric_with_angle != FabricWithAngle(fabric=parent_object.create_fabric(), angle=45.0) + assert fabric_with_angle != FabricWithAngle(fabric=fabric1, angle=55.0) + assert fabric_with_angle == FabricWithAngle(fabric=fabric1, angle=45.0) diff --git a/tests/unittests/test_sublaminate.py b/tests/unittests/test_sublaminate.py index 67fc566c4d..6d7e7725f8 100644 --- a/tests/unittests/test_sublaminate.py +++ b/tests/unittests/test_sublaminate.py @@ -104,3 +104,17 @@ def test_add_lamina(parent_object): assert sublaminate.materials[1].angle == 0.0 assert sublaminate.materials[2].material == fabric1 assert sublaminate.materials[2].angle == -45.0 + + +def test_lamina(parent_object): + fabric1 = parent_object.create_fabric() + fabric1.material = parent_object.create_material() + stackup = parent_object.create_stackup() + stackup.add_fabric(fabric1, angle=30.0) + stackup.add_fabric(fabric1, angle=-30.0) + + lamina = Lamina(material=fabric1, angle=45.0) + assert lamina != Lamina(material=stackup, angle=45.0) + assert lamina != Lamina(material=fabric1, angle=-45.0) + assert lamina == Lamina(material=fabric1, angle=45.0) + print(lamina) diff --git a/tests/unittests/test_virtual_geometry.py b/tests/unittests/test_virtual_geometry.py index b606d3995f..3dd5231934 100644 --- a/tests/unittests/test_virtual_geometry.py +++ b/tests/unittests/test_virtual_geometry.py @@ -100,3 +100,13 @@ def test_virtual_geometry_no_or_invalid_links(parent_object, load_cad_geometry): cad_components=cad_geometry.root_shapes.values(), sub_shapes=[SubShape(cad_geometry=cad_geometry, path="some/path/to/shape")], ) + + +def test_sub_shape(parent_object, load_cad_geometry): + model = parent_object + with load_cad_geometry(model) as cad_geometry: + sub_shape = SubShape(cad_geometry=cad_geometry, path="some/path/to/shape") + with load_cad_geometry(model) as other_cad_geometry: + assert sub_shape != SubShape(cad_geometry=other_cad_geometry, path="some/path/to/shape") + assert sub_shape != SubShape(cad_geometry=cad_geometry, path="some/other/path/to/shape") + assert sub_shape == SubShape(cad_geometry=cad_geometry, path="some/path/to/shape") From bcc80d3e587a7cd042aaf9df4863d78788f46198 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 26 Nov 2024 14:01:38 +0100 Subject: [PATCH 75/96] Implement auto-transfer mode, remove ACPWorkflow (#694) Add the `auto_transfer_files` parameter (default `True`) to `launch_acp`. When enabled, all paths [1] in the PyACP API are relative to the current Python working directory: - with a local instance, the paths are automatically translated to be relative to the server working directory if needed - with a remote instance, files are automatically up- and downloaded, and the paths are translated as needed The `refresh` methods are changed to always require a file path, pointing to the file where the CADGeometry or Imported Solid Model should be refreshed _from_. Remove the `ACPWorkflow` class, since its use is mostly superseded by the auto-transfer mode. Having two different APIs for importing / exporting turned out to be difficult to document, and thus probably also confusing to use. Rework the examples and documentation to match these changes. Other changes: - Rename `remote_filename` to `remote_path` in the download API, since it can contain a directory component in the general case - Remove the `get_composite_post_processing_files` helper, since it was not general enough: in the PyMechanical workflow, the materials file comes from Mechanical. When making it more generic, it does not provide enough benefit over directly using the PyDPF Composites API. - Move `get_dpf_unit_system` to a `dpf_integration_helpers` submodule. [1] with the exception of `external_path` attributes, which are always relative to the server working directory. Closes #693. --- README.rst | 4 +- doc/create_doc_windows.ps1 | 9 +- doc/source/api/dpf_integration_helpers.rst | 9 + doc/source/api/index.rst | 4 +- doc/source/api/internal.rst | 1 - doc/source/api/other_utils.rst | 2 + doc/source/api/workflow.rst | 13 - doc/source/intro.rst | 18 +- .../concepts/material_property_sets.rst | 5 +- doc/source/user_guide/concepts/store.rst | 7 +- .../user_guide/howto/create_input_file.rst | 8 +- .../user_guide/howto/file_management.rst | 166 +++---- doc/source/user_guide/howto/print_model.rst | 3 +- .../howto/view_model_in_acp_gui.rst | 3 +- .../user_guide/howto/visualize_model.rst | 7 +- .../config/vocabularies/ANSYS/accept.txt | 1 + examples/modeling_features/001-materials.py | 21 +- .../01-sandwich-panel-layup.py | 23 +- .../02-simple-selection-rules.py | 17 +- examples/modeling_features/020-solid_model.py | 18 +- .../03-advanced-selection-rules.py | 22 +- .../04-layup-thickness-definitions.py | 18 +- .../05-rosettes-ply-directions.py | 16 +- .../06-ply-direction-lookup-table.py | 16 +- .../use_cases/01-optimizing-ply-angles.py | 67 +-- examples/workflows/01-pymapdl-workflow.py | 43 +- .../workflows/02-advanced-pymapdl-workflow.py | 34 +- .../03-pymechanical-shell-workflow.py | 20 +- .../04-pymechanical-solid-workflow.py | 22 +- .../05-pymechanical-to-cdb-workflow.py | 17 +- src/ansys/acp/core/__init__.py | 13 +- src/ansys/acp/core/_server/acp_instance.py | 134 +++++- src/ansys/acp/core/_server/launch.py | 38 +- .../core/_tree_objects/_solid_model_export.py | 33 +- src/ansys/acp/core/_tree_objects/base.py | 31 +- .../acp/core/_tree_objects/cad_geometry.py | 3 +- .../_tree_objects/imported_solid_model.py | 3 +- src/ansys/acp/core/_tree_objects/model.py | 136 +++--- src/ansys/acp/core/_workflow.py | 446 ------------------ src/ansys/acp/core/dpf_integration_helpers.py | 72 +++ src/ansys/acp/core/extras/example_helpers.py | 118 +++-- tests/benchmarks/test_class40.py | 4 +- tests/conftest.py | 42 +- tests/unittests/test_acp_instance.py | 35 ++ tests/unittests/test_cad_geometry.py | 13 +- tests/unittests/test_edge_property_list.py | 5 +- tests/unittests/test_imported_solid_model.py | 96 ++-- tests/unittests/test_model.py | 128 ++--- tests/unittests/test_solid_model.py | 21 +- tests/unittests/test_tree_printer.py | 4 +- tests/unittests/test_workflow.py | 206 -------- 51 files changed, 763 insertions(+), 1432 deletions(-) create mode 100644 doc/source/api/dpf_integration_helpers.rst delete mode 100644 doc/source/api/workflow.rst delete mode 100644 src/ansys/acp/core/_workflow.py create mode 100644 src/ansys/acp/core/dpf_integration_helpers.py delete mode 100644 tests/unittests/test_workflow.py diff --git a/README.rst b/README.rst index 0753637ba8..8d6a9761d8 100644 --- a/README.rst +++ b/README.rst @@ -78,8 +78,8 @@ Model from an existing file: .. code-block:: pycon - >>> remote_filename = acp.upload_file(local_path="") - >>> model = acp.import_model(path=remote_filename) + >>> remote_path = acp.upload_file(local_path="") + >>> model = acp.import_model(path=remote_path) >>> model.name 'ACP Model' diff --git a/doc/create_doc_windows.ps1 b/doc/create_doc_windows.ps1 index 00c3ed05ec..8ba39b514f 100644 --- a/doc/create_doc_windows.ps1 +++ b/doc/create_doc_windows.ps1 @@ -22,8 +22,11 @@ $Env:PYACP_DOC_SKIP_GALLERY=0 # whether to skip the API documentation $Env:PYACP_DOC_SKIP_API=0 -docker-compose -f ./docker-compose/docker-compose-extras.yaml up -d +$ParentDir = Split-Path -Parent $PSScriptRoot +$DockerComposeFile = Join-Path -Path $ParentDir -ChildPath "docker-compose/docker-compose-extras.yaml" +docker compose -f $DockerComposeFile up -d -.\doc\make.bat html +$MakeFile = Join-Path -Path $PSScriptRoot -ChildPath "make.bat" +& $MakeFile html -docker-compose -f ./docker-compose/docker-compose-extras.yaml down +docker compose -f $DockerComposeFile down diff --git a/doc/source/api/dpf_integration_helpers.rst b/doc/source/api/dpf_integration_helpers.rst new file mode 100644 index 0000000000..f31e1b4072 --- /dev/null +++ b/doc/source/api/dpf_integration_helpers.rst @@ -0,0 +1,9 @@ +DPF integration helpers +----------------------- + +.. currentmodule:: ansys.acp.core.dpf_integration_helpers + +.. autosummary:: + :toctree: _autosummary + + get_dpf_unit_system diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 1c86254675..bdfc694178 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -23,9 +23,9 @@ API reference other_types plot_utils other_utils - workflow - example_helpers + dpf_integration_helpers mechanical_integration_helpers + example_helpers internal {% else %} The API reference is not available in this documentation build. diff --git a/doc/source/api/internal.rst b/doc/source/api/internal.rst index a11657001b..a35d04a211 100644 --- a/doc/source/api/internal.rst +++ b/doc/source/api/internal.rst @@ -35,4 +35,3 @@ Internal objects _tree_objects.base.TreeObjectBase _tree_objects.material.property_sets.wrapper.TC _tree_objects.material.property_sets.wrapper.TV - _workflow._LocalWorkingDir diff --git a/doc/source/api/other_utils.rst b/doc/source/api/other_utils.rst index 5a16ddd96b..ea0ca088aa 100644 --- a/doc/source/api/other_utils.rst +++ b/doc/source/api/other_utils.rst @@ -6,4 +6,6 @@ Other utilities .. autosummary:: :toctree: _autosummary + get_model_tree + print_model recursive_copy diff --git a/doc/source/api/workflow.rst b/doc/source/api/workflow.rst deleted file mode 100644 index 4ed3357967..0000000000 --- a/doc/source/api/workflow.rst +++ /dev/null @@ -1,13 +0,0 @@ -Workflow --------- - -.. currentmodule:: ansys.acp.core - -.. autosummary:: - :toctree: _autosummary - - ACPWorkflow - get_composite_post_processing_files - get_model_tree - get_dpf_unit_system - print_model diff --git a/doc/source/intro.rst b/doc/source/intro.rst index d69fef97d8..e06d2026fc 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -57,27 +57,23 @@ Get a model You can resume a model from an existing ACP DB (ACPH5) or built it from scratch by importing an FE model (mesh). -To load an existing model with PyACP, use the :meth:`.ACPWorkflow.from_acph5_file` method: +To load an existing model with PyACP, use the :meth:`.import_model` method: .. testcode:: - workflow = pyacp.ACPWorkflow.from_acph5_file( - acp=acp, - acph5_file_path="model.acph5", - ) - model = workflow.model + model = acp.import_model("model.acph5") -To import an FE model, use the :meth:`.ACPWorkflow.from_cdb_or_dat_file` method. +To import an FE model, use the ``format="ansys:cdb"`` or ``format="ansys:dat"`` +parameter, respectively. The following example imports a CDB file. .. testcode:: - workflow = pyacp.ACPWorkflow.from_cdb_or_dat_file( - acp=acp, - cdb_or_dat_file_path="model.cdb", + model = acp.import_model( + "model.cdb", + format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA, ) - model = workflow.model .. testcode:: :hide: diff --git a/doc/source/user_guide/concepts/material_property_sets.rst b/doc/source/user_guide/concepts/material_property_sets.rst index 0985ce43c3..69b4287d9d 100644 --- a/doc/source/user_guide/concepts/material_property_sets.rst +++ b/doc/source/user_guide/concepts/material_property_sets.rst @@ -70,7 +70,7 @@ you must redefine the stress and strain limits. For engineering constants, however, the orthotropic and isotropic definitions are interlinked. Therefore, when you change the ply type of a material, the engineering constants are automatically converted. -To avoid accidental use of incorrect engineering constants, PyACP enforces +To avoid accidental use of incorrect engineering constants, PyACP enforces conversion and assignment rules, as described later on this page. Conversion rules @@ -94,8 +94,7 @@ The following rules apply when changing the ply type of a material: >>> import ansys.acp.core as pyacp >>> acp = pyacp.launch_acp() - >>> path = acp.upload_file("../tests/data/minimal_complete_model_no_matml_link.acph5") - >>> model = acp.import_model(path=path) + >>> model = acp.import_model("../tests/data/minimal_complete_model_no_matml_link.acph5") Consider the following example: diff --git a/doc/source/user_guide/concepts/store.rst b/doc/source/user_guide/concepts/store.rst index 95aa389b70..878d276b10 100644 --- a/doc/source/user_guide/concepts/store.rst +++ b/doc/source/user_guide/concepts/store.rst @@ -29,7 +29,7 @@ Consider the following example. First, launch an ACP instance and import a model .. testcode:: :hide: - path = acp.upload_file("../tests/data/minimal_complete_model_no_matml_link.acph5") + path = "../tests/data/minimal_complete_model_no_matml_link.acph5" .. doctest:: @@ -92,11 +92,6 @@ You may also use the :meth:`clone <.Material.clone>` method to copy an object be >>> acp2 = pyacp.launch_acp() -.. testcode:: - :hide: - - path = acp2.upload_file("../tests/data/minimal_complete_model_no_matml_link.acph5") - .. doctest:: >>> # path = ... # path to another model file diff --git a/doc/source/user_guide/howto/create_input_file.rst b/doc/source/user_guide/howto/create_input_file.rst index da40fedff7..758775e499 100644 --- a/doc/source/user_guide/howto/create_input_file.rst +++ b/doc/source/user_guide/howto/create_input_file.rst @@ -6,8 +6,8 @@ Create input file for PyACP To start working with PyACP, an input file that contains the mesh is required. PyACP supports reading the mesh from: -#. CDB or DAT files, using either the :meth:`.ACPWorkflow.from_cdb_or_dat_file()` method, or the :meth:`.ACPInstance.import_model()` method with the ``"ansys::cdb"`` or ``"ansys:dat"`` format. -#. HDF5 transfer files generated by Mechanical, using the :meth:`.ACPWorkflow.from_mechanical_h5_file()` method or the :meth:`.ACPInstance.import_model()` method with the ``"ansys::h5"`` format. +#. CDB or DAT files, using the :meth:`.ACPInstance.import_model()` method with the ``"ansys::cdb"`` or ``"ansys:dat"`` format. +#. HDF5 transfer files generated by Mechanical, using the :meth:`.ACPInstance.import_model()` method with the ``"ansys::h5"`` format. PyACP reads the mesh, including any coordinate systems, element sets, and edge sets from the input file. In the case of CDB or DAT files, PyACP also reads the materials @@ -47,7 +47,6 @@ One way to create an input file for PyACP is to create a static structural setup * Save the input file to a DAT file in the desired location. -The created input file can be read with the :meth:`.ACPWorkflow.from_cdb_or_dat_file` method. For a complete example, see :ref:`pymapdl_workflow_example`. .. note:: @@ -73,8 +72,7 @@ You can also create an input file for PyACP by performing these steps: CDWRITE,ALL,FILE,cdbfile.cdb -The created input file can be read with :meth:`.ACPWorkflow.from_cdb_or_dat_file`. See -:ref:`pymapdl_workflow_example` for a complete example. +See :ref:`pymapdl_workflow_example` for a complete example. Notes on material handling ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/user_guide/howto/file_management.rst b/doc/source/user_guide/howto/file_management.rst index d79e11bf1c..fb8f06c0f8 100644 --- a/doc/source/user_guide/howto/file_management.rst +++ b/doc/source/user_guide/howto/file_management.rst @@ -1,13 +1,5 @@ Manage input and output files ------------------------------ - -When defining your workflow using PyACP and other tools, you may need control -over where the input and output files are stored. This guide shows two -ways to manage them. - -In the following examples, the ACP instance is launched on a remote server. The -differences between local and remote ACP instances, in terms of file management, -are explained afterwards in :ref:`local_vs_remote`. +============================= .. doctest:: :hide: @@ -25,94 +17,82 @@ are explained afterwards in :ref:`local_vs_remote`. >>> doctest_tempdir = tempfile.TemporaryDirectory() >>> os.chdir(doctest_tempdir.name) +When defining your workflow using PyACP and other tools, you may need control +over where the input and output files are stored. There are two main ways to +manage files: auto-transfer mode and manual file management. -.. doctest:: +.. note:: - >>> import ansys.acp.core as pyacp - >>> acp = pyacp.launch_acp() + When using a local ACP instance (``"direct"`` launch mode), the auto-transfer + and manual modes are identical, as long as the current working directory is + not changed after launching the ACP instance. +Auto-transfer mode +------------------ -Using a predefined workflow -''''''''''''''''''''''''''' +When passing the ``auto_transfer_files=True`` parameter to :func:`.launch_acp` +(the default behavior), PyACP automatically uploads files to the ACP instance +and downloads output files to the local machine. -The simplest way to manage files is by using the :class:`.ACPWorkflow` class. This class -uses predetermined filenames and automatically handles uploading and downloading files. +Paths passed to the PyACP functions are all paths on the local machine. They +can be either absolute paths, or relative to the current working directory of +the Python instance. -Loading input files -~~~~~~~~~~~~~~~~~~~ +The only exception is the ``external_path`` attribute of the :class:`.CADGeometry` +and :class:`.ImportedSolidModel` classes. This attribute refers to a path on the +server side. It can again be an absolute path, or relative to the ACP instance's +working directory. +You can instead use the :meth:`.CADGeometry.refresh` and +:meth:`.ImportedSolidModel.refresh` methods to define the input file, which also +handles the upload automatically. -To get started with loading input files, you must define a workflow using either an -FE model (CDB or DAT) file or an ACP model (ACPH5) file. +.. note:: -The following example assumes that you have a directory, ``DATA_DIRECTORY``, that contains an ``input_file.cdb`` file. + On local ACP instances, the up- and download methods simply convert the + paths to be relative to the ACP instance's working directory if needed. -.. doctest:: - >>> DATA_DIRECTORY - PosixPath('...') - >>> list(DATA_DIRECTORY.iterdir()) - [PosixPath('.../input_file.cdb')] +Loading input files +~~~~~~~~~~~~~~~~~~~ -Create an :class:`.ACPWorkflow` instance that works with this file using -the :meth:`.ACPWorkflow.from_cdb_or_dat_file` method: +To load an input file, pass the file path on your local machine to the +:meth:`.import_model` method: .. doctest:: - >>> workflow = pyacp.ACPWorkflow.from_cdb_or_dat_file( - ... acp=acp, - ... cdb_or_dat_file_path=DATA_DIRECTORY / "input_file.cdb", + >>> import ansys.acp.core as pyacp + >>> acp = pyacp.launch_acp() + >>> # DATA_DIRECTORY is a directory containing the input file + >>> model = acp.import_model( + ... DATA_DIRECTORY / "input_file.cdb", + ... format="ansys:cdb", ... unit_system=pyacp.UnitSystemType.MPA, ... ) - -That uploads the file to the ACP instance and creates a model from it. You -can access the newly created model using the ``workflow.model`` command: - -.. doctest:: - - >>> workflow.model + >>> model Getting output files ~~~~~~~~~~~~~~~~~~~~ -Use the workflow's ``get_local_*()`` methods to create and download -output files. For example, to get the ACPH5 file of the model, use the -:meth:`.get_local_acph5_file` method: - -.. doctest:: - - >>> model = workflow.model - >>> model.name = "My model" - >>> workflow.get_local_acph5_file() - PosixPath('/tmp/.../My model.acph5') - -Note that the filename is based on the model name. - -Using a custom working directory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, the output files are stored in a temporary directory. You can -specify a custom working directory using the ``local_working_directory`` argument of -the :class:`.ACPWorkflow` constructor: +When getting output files, pass the desired path on your local machine to the +export / save method. .. doctest:: - >>> workflow = pyacp.ACPWorkflow.from_cdb_or_dat_file( - ... acp=acp, - ... cdb_or_dat_file_path=DATA_DIRECTORY / "input_file.cdb", - ... unit_system=pyacp.UnitSystemType.MPA, - ... local_working_directory=pathlib.Path("."), - ... ) - -Any produced output files are now stored in the custom working directory. Input files -are also copied to this directory before being uploaded to the ACP instance. + >>> import os + >>> model.save("output_file.acph5") + >>> "output_file.acph5" in os.listdir() + True Manual file management -'''''''''''''''''''''' +---------------------- -To get more control over where files are stored, you can manually upload and -download them to the server and specify their names. +When passing ``auto_transfer_files=False`` to :func:`.launch_acp`, PyACP does not +automatically upload or download files. + +In this case, you need to manually manage the up- and download of files, as +described in the following sections. Loading input files ~~~~~~~~~~~~~~~~~~~ @@ -122,6 +102,7 @@ using the :meth:`.upload_file` method: .. doctest:: + >>> acp = pyacp.launch_acp(auto_transfer_files=False) >>> uploaded_path = acp.upload_file(DATA_DIRECTORY / "input_file.cdb") >>> uploaded_path PurePosixPath('input_file.cdb') @@ -145,9 +126,17 @@ Getting output files To get the ACPH5 file, it must be stored on the server. You can manually do that using the model's :meth:`.save` method: +.. doctest:: + :hide: + + >>> # need to delete the file since it was created in the previous example + >>> pathlib.Path("output_file.acph5").unlink(missing_ok=True) + .. doctest:: >>> model.save("output_file.acph5") + >>> "output_file.acph5" in os.listdir() + False Then, you can download the file using the :meth:`.download_file` method of the ACP instance: @@ -155,42 +144,11 @@ instance: .. doctest:: >>> acp.download_file( - ... remote_filename="output_file.acph5", local_path="output_file_downloaded.acph5" + ... remote_path="output_file.acph5", + ... local_path="output_file_downloaded.acph5", ... ) - - -.. _local_vs_remote: - -Local versus remote ACP instance -'''''''''''''''''''''''''''''''' - -In the preceding examples, ACP ran on a remote server. However, -you can also launch ACP as a process on your local machine. For information on launching -ACP locally, see :ref:`launch_configuration`. - -When the ACP instance is local, you can use the same code described previously. However, -the effects are slightly different: - -When using a workflow -~~~~~~~~~~~~~~~~~~~~~ - -- The input file is still copied to the ``local_working_directory`` argument, but then it is loaded - directly into the ACP instance. There is no separate upload step. -- The output files are stored in the ``local_working_directory`` argument by default. - - -When using manual upload and download -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- The :meth:`.upload_file` method has no effect and simply returns the input file path. -- The :meth:`.download_file` method copies the file to the specified ``local_path`` argument if - the ``local_path`` and ``remote_filename`` arguments are not the same. - -.. hint:: - - Even when they have no effect, it is good practice to include the upload and download - steps in your code. That way, both local and remote ACP instances can use it. - + >>> "output_file_downloaded.acph5" in os.listdir() + True .. doctest:: :hide: diff --git a/doc/source/user_guide/howto/print_model.rst b/doc/source/user_guide/howto/print_model.rst index 70ec3c084f..658bd3f7a7 100644 --- a/doc/source/user_guide/howto/print_model.rst +++ b/doc/source/user_guide/howto/print_model.rst @@ -8,8 +8,7 @@ A tree structure gives an overview of an ACP model. To print a model's tree stru import ansys.acp.core as pyacp acp = pyacp.launch_acp() - path = acp.upload_file("../tests/data/minimal_complete_model_no_matml_link.acph5") - model = acp.import_model(path=path) + model = acp.import_model("../tests/data/minimal_complete_model_no_matml_link.acph5") .. doctest:: diff --git a/doc/source/user_guide/howto/view_model_in_acp_gui.rst b/doc/source/user_guide/howto/view_model_in_acp_gui.rst index 0c262960c6..b082d77c40 100644 --- a/doc/source/user_guide/howto/view_model_in_acp_gui.rst +++ b/doc/source/user_guide/howto/view_model_in_acp_gui.rst @@ -3,7 +3,6 @@ View model in ACP GUI --------------------- -To view the PyACP model in the ACP GUI, save the model to a file using the :meth:`.ACPWorkflow.get_local_acph5_file` method and then open the saved file in ACP by selecting **File > Open**. +To view the PyACP model in the ACP GUI, save the model to a file using the :meth:`.Model.save` method and then open the saved file in ACP by selecting **File > Open**. In the ACP GUI, reload the model from its context menu by right-clicking the model's name in the tree and selecting **Reload Model**. A common workflow is to save the model to a file at the end of the script and reload it in ACP after each script run. - diff --git a/doc/source/user_guide/howto/visualize_model.rst b/doc/source/user_guide/howto/visualize_model.rst index b3d747878c..b86c14ee04 100644 --- a/doc/source/user_guide/howto/visualize_model.rst +++ b/doc/source/user_guide/howto/visualize_model.rst @@ -20,14 +20,13 @@ Visualize model >>> input_file = get_example_file( ... ExampleKeys.RACE_CAR_NOSE_ACPH5, pathlib.Path(tempdir.name) ... ) - >>> path = acp.upload_file(input_file) - >>> model = acp.import_model(path) + >>> model = acp.import_model(input_file) >>> input_file_geometry = get_example_file( ... ExampleKeys.RACE_CAR_NOSE_STEP, pathlib.Path(tempdir.name) ... ) - >>> path_geometry = acp.upload_file(input_file_geometry) - >>> model.create_cad_geometry(name="nose_geometry", external_path=path_geometry) + >>> cad_geometry = model.create_cad_geometry(name="nose_geometry") + >>> cad_geometry.refresh(input_file_geometry) >>> model.update() diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index 0563510018..2d0ac40ac8 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -9,3 +9,4 @@ Mechanical APDL API untrusted +DPF diff --git a/examples/modeling_features/001-materials.py b/examples/modeling_features/001-materials.py index d57f5c937a..32bbb51029 100644 --- a/examples/modeling_features/001-materials.py +++ b/examples/modeling_features/001-materials.py @@ -40,7 +40,6 @@ Fabrics, Stackups and Sublaminates can be used to create plies. It is recommended to look a the Ansys help for more information on the different types of materials. """ -import os # %% # Import the standard library and third-party dependencies. @@ -49,7 +48,7 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import ACPWorkflow, FabricWithAngle, Lamina, PlyType, SymmetryType, launch_acp +from ansys.acp.core import FabricWithAngle, Lamina, PlyType, SymmetryType, launch_acp from ansys.acp.core.extras import ExampleKeys, get_example_file from ansys.acp.core.material_property_sets import ( ConstantEngineeringConstants, @@ -74,14 +73,8 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp, - cdb_or_dat_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model +# Import the model from the input file. +model = acp.import_model(input_file, format="ansys:dat") # %% # Create a Material @@ -178,11 +171,9 @@ # An alternative is to load materials from an Engineering Data # file via :meth:`.Model.import_materials`. engd_file_path = get_example_file(ExampleKeys.MATERIALS_XML, WORKING_DIR) -remote_engd_file_path = acp.upload_file(engd_file_path) -model.import_materials(matml_path=remote_engd_file_path) +model.import_materials(matml_path=engd_file_path) +model.materials # %% # Some workflows require the materials to be exported to an XML file. -engd_file_name = "exported_materials.xml" -model.export_materials(path=engd_file_name) -acp.download_file(engd_file_name, os.path.join(WORKING_DIR, engd_file_name)) +model.export_materials(path=WORKING_DIR / "exported_materials.xml") diff --git a/examples/modeling_features/01-sandwich-panel-layup.py b/examples/modeling_features/01-sandwich-panel-layup.py index 6679f921af..3d19baf950 100644 --- a/examples/modeling_features/01-sandwich-panel-layup.py +++ b/examples/modeling_features/01-sandwich-panel-layup.py @@ -41,7 +41,6 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ( - ACPWorkflow, FabricWithAngle, Lamina, PlyType, @@ -66,22 +65,16 @@ input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_DAT, WORKING_DIR) # %% -# Launch the PyACP server and connect to it. +# Launch the ACP server and connect to it. acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -# The ``ACPWorkflow`` class provides convenience methods that simplify file handling. -# It automatically creates a model based on the input file. - -workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp, - cdb_or_dat_file_path=input_file, - local_working_directory=WORKING_DIR, -) +# Define the input file and import it into ACP. -model = workflow.model -print(workflow.working_directory.path) +model = acp.import_model( + input_file, + format="ansys:cdb", +) print(model.unit_system) # %% @@ -208,11 +201,11 @@ # %% # Update and print the model. model.update() -print_model(workflow.model) +print_model(model) # sphinx_gallery_start_ignore from ansys.acp.core.extras.example_helpers import _run_analysis # Run the analysis to ensure that all the material properties have been correctly # defined. -_run_analysis(workflow) +_run_analysis(model) # sphinx_gallery_end_ignore diff --git a/examples/modeling_features/02-simple-selection-rules.py b/examples/modeling_features/02-simple-selection-rules.py index 157827f158..759caa7b85 100644 --- a/examples/modeling_features/02-simple-selection-rules.py +++ b/examples/modeling_features/02-simple-selection-rules.py @@ -44,7 +44,7 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import ACPWorkflow, LinkedSelectionRule, launch_acp +from ansys.acp.core import LinkedSelectionRule, launch_acp from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = -1 @@ -66,19 +66,8 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -# The ``ACPWorkflow`` class provides convenience methods that simplify file handling. -# It automatically creates a model based on the input file. -# This example's input file contains a flat plate with a single ply. - -workflow = ACPWorkflow.from_acph5_file( - acp=acp, - acph5_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model -print(workflow.working_directory.path) +# Load the model from the input file. +model = acp.import_model(input_file) print(model.unit_system) # %% diff --git a/examples/modeling_features/020-solid_model.py b/examples/modeling_features/020-solid_model.py index 06a0cdd808..9a62941a45 100644 --- a/examples/modeling_features/020-solid_model.py +++ b/examples/modeling_features/020-solid_model.py @@ -43,7 +43,6 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ( - ACPWorkflow, CADGeometry, CutOffGeometryOrientationType, EdgeSetType, @@ -70,14 +69,8 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -workflow = ACPWorkflow.from_acph5_file( - acp=acp, - acph5_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model +# Load the model from the input file. +model = acp.import_model(input_file) # %% # Create a simple layup @@ -107,8 +100,9 @@ def create_virtual_geometry_from_file( ) -> tuple[CADGeometry, VirtualGeometry]: """Create a CAD geometry and virtual geometry.""" geometry_file = get_example_file(example_key, WORKING_DIR) - geometry_obj = workflow.add_cad_geometry_from_local_file(geometry_file) - workflow.model.update() + geometry_obj = model.create_cad_geometry() + geometry_obj.refresh(geometry_file) # upload and load the geometry file + model.update() virtual_geometry = model.create_virtual_geometry( name="thickness_virtual_geometry", cad_components=geometry_obj.root_shapes.values() ) @@ -123,7 +117,7 @@ def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float edges = geom_mesh.extract_feature_edges() plotter.add_mesh(edges, color="white", line_width=4) plotter.add_mesh(edges, color="black", line_width=2) - plotter.add_mesh(workflow.model.solid_mesh.to_pyvista(), show_edges=True) + plotter.add_mesh(model.solid_mesh.to_pyvista(), show_edges=True) plotter.show() diff --git a/examples/modeling_features/03-advanced-selection-rules.py b/examples/modeling_features/03-advanced-selection-rules.py index 8989b257ad..17a0743697 100644 --- a/examples/modeling_features/03-advanced-selection-rules.py +++ b/examples/modeling_features/03-advanced-selection-rules.py @@ -50,7 +50,6 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ( - ACPWorkflow, BooleanOperationType, DimensionType, EdgeSetType, @@ -77,19 +76,9 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -# The ``ACPWorkflow`` class provides convenience methods that simplify file handling. -# It automatically creates a model based on the input file. -# This example's input file contains a flat plate with a single ply. +# Load the model from the input file. -workflow = ACPWorkflow.from_acph5_file( - acp=acp, - acph5_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model -print(workflow.working_directory.path) +model = acp.import_model(input_file) print(model.unit_system) # %% @@ -155,8 +144,8 @@ # %% # Add a CAD geometry to the model. triangle_path = get_example_file(ExampleKeys.RULE_GEOMETRY_TRIANGLE, WORKING_DIR) -triangle = workflow.add_cad_geometry_from_local_file(triangle_path) - +triangle = model.create_cad_geometry() +triangle.refresh(triangle_path) # Note: It is important to update the model here, because the root_shapes of the # cad_geometry are not available until the model is updated. @@ -196,7 +185,8 @@ # %% # Add the cutoff CAD geometry to the model. cutoff_plane_path = get_example_file(ExampleKeys.CUT_OFF_GEOMETRY, WORKING_DIR) -cut_off_plane = workflow.add_cad_geometry_from_local_file(cutoff_plane_path) +cut_off_plane = model.create_cad_geometry() +cut_off_plane.refresh(cutoff_plane_path) # Note: It is important to update the model here, because the root_shapes of the # cad_geometry are not available until the model is updated. diff --git a/examples/modeling_features/04-layup-thickness-definitions.py b/examples/modeling_features/04-layup-thickness-definitions.py index 4734dbbb94..b0a3884e70 100644 --- a/examples/modeling_features/04-layup-thickness-definitions.py +++ b/examples/modeling_features/04-layup-thickness-definitions.py @@ -45,7 +45,7 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import ACPWorkflow, DimensionType, ThicknessType, launch_acp +from ansys.acp.core import DimensionType, ThicknessType, launch_acp from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 2 @@ -66,18 +66,9 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -# The ``ACPWorkflow`` class provides convenience methods that simplify file handling. -# It automatically creates a model based on the input file. +# Load the model from the input file. # This example's input file contains a flat plate with a single ply. -workflow = ACPWorkflow.from_acph5_file( - acp=acp, - acph5_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model -print(workflow.working_directory.path) +model = acp.import_model(input_file) print(model.unit_system) @@ -94,7 +85,8 @@ # %% # Add the solid geometry to the model that defines the thickness. thickness_geometry_file = get_example_file(ExampleKeys.THICKNESS_GEOMETRY, WORKING_DIR) -thickness_geometry = workflow.add_cad_geometry_from_local_file(thickness_geometry_file) +thickness_geometry = model.create_cad_geometry() +thickness_geometry.refresh(thickness_geometry_file) # Note: It is important to update the model here, because the root_shapes of the # cad_geometry are not available until the model is updated. diff --git a/examples/modeling_features/05-rosettes-ply-directions.py b/examples/modeling_features/05-rosettes-ply-directions.py index cdef2889f1..b49c921f52 100644 --- a/examples/modeling_features/05-rosettes-ply-directions.py +++ b/examples/modeling_features/05-rosettes-ply-directions.py @@ -44,7 +44,6 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ( - ACPWorkflow, EdgeSetType, PlyType, RosetteSelectionMethod, @@ -71,18 +70,9 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -# The ``ACPWorkflow`` class provides convenience methods that simplify file handling. -# It automatically creates a model based on the input file. -# This example's input file contains a flat plate with a single ply. -workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp, - cdb_or_dat_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model -print(workflow.working_directory.path) +# Import the model from the input file. +# This example's input file contains a flat plate. +model = acp.import_model(input_file, format="ansys:dat") print(model.unit_system) # %% diff --git a/examples/modeling_features/06-ply-direction-lookup-table.py b/examples/modeling_features/06-ply-direction-lookup-table.py index 5c07777801..38813bd603 100644 --- a/examples/modeling_features/06-ply-direction-lookup-table.py +++ b/examples/modeling_features/06-ply-direction-lookup-table.py @@ -45,7 +45,6 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ( - ACPWorkflow, DimensionType, DrapingType, LookUpTableColumnValueType, @@ -71,18 +70,9 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -# The ``ACPWorkflow`` class provides convenience methods that simplify the handling. -# It automatically creates a model based on the input file. -# This example's input file contains a flat plate with a single ply. -workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp, - cdb_or_dat_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model -print(workflow.working_directory.path) +# Import the model from the input file. +# This example's input file contains a flat plate. +model = acp.import_model(input_file, format="ansys:dat") print(model.unit_system) # %% diff --git a/examples/use_cases/01-optimizing-ply-angles.py b/examples/use_cases/01-optimizing-ply-angles.py index 7c95254fcc..eb4b704adf 100644 --- a/examples/use_cases/01-optimizing-ply-angles.py +++ b/examples/use_cases/01-optimizing-ply-angles.py @@ -95,8 +95,7 @@ # # The ``prepare_acp_model`` function imports the ``optimization_model.dat`` file into a new # ACP model and creates a lay-up with six plies. -# It returns a :class:`.ACPWorkflow` object that can be used to access the model and -# generate the output files. +# It returns the :class:`.Model` instance. input_file = get_example_file( example_key=ExampleKeys.OPTIMIZATION_EXAMPLE_DAT, @@ -104,14 +103,12 @@ ) -def prepare_acp_model(*, acp, workdir, input_file): +def prepare_acp_model(*, acp, input_file): # Import the DAT input file into a new ACP model - acp_workflow = pyacp.ACPWorkflow.from_cdb_or_dat_file( - acp=acp, - cdb_or_dat_file_path=input_file, - local_working_directory=workdir, + model = acp.import_model( + input_file, + format="ansys:dat", ) - model = acp_workflow.model model.name = "optimization_example" element_set = model.element_sets["All_Elements"] @@ -171,16 +168,16 @@ def prepare_acp_model(*, acp, workdir, input_file): oriented_selection_sets=[oss], number_of_layers=1, ) - acp_workflow.model.update() - return acp_workflow + model.update() + return model # %% # Create the ACP model and visualize the first ply's fiber direction. -acp_workflow = prepare_acp_model(acp=acp_instance, workdir=workdir, input_file=input_file) -ply = list(acp_workflow.model.modeling_groups["Modeling_Group"].modeling_plies.values())[0] +model = prepare_acp_model(acp=acp_instance, input_file=input_file) +ply = list(model.modeling_groups["Modeling_Group"].modeling_plies.values())[0] pyacp.get_directions_plotter( - model=acp_workflow.model, + model=model, components=[ply.elemental_data.fiber_direction], length_factor=5.0, culling_factor=5, @@ -197,8 +194,7 @@ def prepare_acp_model(*, acp, workdir, input_file): # updates the model. -def update_ply_angles(*, acp_workflow, parameters): - model = acp_workflow.model +def update_ply_angles(*, model, parameters): modeling_plies = list(model.modeling_groups["Modeling_Group"].modeling_plies.values()) assert len(modeling_plies) == len(parameters) for angle, modeling_ply in zip(parameters, modeling_plies): @@ -207,7 +203,7 @@ def update_ply_angles(*, acp_workflow, parameters): model.update() -update_ply_angles(acp_workflow=acp_workflow, parameters=[0, 45, 90, 135, 180, 225]) +update_ply_angles(model=model, parameters=[0, 45, 90, 135, 180, 225]) # %% @@ -216,7 +212,7 @@ def update_ply_angles(*, acp_workflow, parameters): def solve_cdb(*, mapdl, cdb_file, workdir): mapdl.clear() - mapdl.input(cdb_file) + mapdl.input(str(cdb_file)) # Solve the model. Note that the model contains two timesteps. mapdl.allsel() mapdl.slashsolu() @@ -232,8 +228,9 @@ def solve_cdb(*, mapdl, cdb_file, workdir): return rst_file_local_path -cdb_file = acp_workflow.get_local_cdb_file() -rst_file = solve_cdb(mapdl=mapdl, cdb_file=cdb_file, workdir=workdir) +cdb_file_path = workdir / "optimization_example.cdb" +model.export_analysis_model(cdb_file_path) +rst_file = solve_cdb(mapdl=mapdl, cdb_file=cdb_file_path, workdir=workdir) # %% # The ``get_max_irf()`` function uses PyDPF Composites to calculate the maximum @@ -247,20 +244,29 @@ def solve_cdb(*, mapdl, cdb_file, workdir): name="Combined Failure Criterion", failure_criteria=[max_stress_criterion], ) +materials_file_path = workdir / "materials.xml" +model.export_materials(materials_file_path) def get_max_irf( *, - acp_workflow, + model, dpf_server, rst_file, failure_criterion, ): + composite_definitions_file = workdir / "ACPCompositeDefinitions.h5" + model.export_shell_composite_definitions(composite_definitions_file) # Create the composite model and configure its input composite_model = pydpf_composites.composite_model.CompositeModel( - composite_files=pyacp.get_composite_post_processing_files( - acp_workflow=acp_workflow, - local_rst_file_path=rst_file, + composite_files=pydpf_composites.data_sources.ContinuousFiberCompositesFiles( + rst=rst_file, + composite={ + "shell": pydpf_composites.data_sources.CompositeDefinitionFiles( + composite_definitions_file + ) + }, + engineering_data=materials_file_path, ), server=dpf_server, ) @@ -282,7 +288,7 @@ def get_max_irf_for_time(time): get_max_irf( - acp_workflow=acp_workflow, + model=model, dpf_server=dpf_server, rst_file=rst_file, failure_criterion=combined_failure_criterion, @@ -299,13 +305,14 @@ def get_max_irf_for_time(time): def get_max_irf_for_parameters( - parameters, *, acp_workflow, mapdl, dpf_server, failure_criterion, workdir, results + parameters, *, model, mapdl, dpf_server, failure_criterion, workdir, results ): - update_ply_angles(acp_workflow=acp_workflow, parameters=parameters) - cdb_file = acp_workflow.get_local_cdb_file() - rst_file = solve_cdb(mapdl=mapdl, cdb_file=cdb_file, workdir=workdir) + update_ply_angles(model=model, parameters=parameters) + cdb_file_path = workdir / "optimization_example.cdb" + model.export_analysis_model(cdb_file_path) + rst_file = solve_cdb(mapdl=mapdl, cdb_file=cdb_file_path, workdir=workdir) res = get_max_irf( - acp_workflow=acp_workflow, + model=model, dpf_server=dpf_server, rst_file=rst_file, failure_criterion=failure_criterion, @@ -322,7 +329,7 @@ def get_max_irf_for_parameters( results: list[float] = [] optimization_function = partial( get_max_irf_for_parameters, - acp_workflow=acp_workflow, + model=model, mapdl=mapdl, dpf_server=dpf_server, failure_criterion=combined_failure_criterion, diff --git a/examples/workflows/01-pymapdl-workflow.py b/examples/workflows/01-pymapdl-workflow.py index 5da9a93c67..10df7f2274 100644 --- a/examples/workflows/01-pymapdl-workflow.py +++ b/examples/workflows/01-pymapdl-workflow.py @@ -52,11 +52,9 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ( - ACPWorkflow, PlyType, - get_composite_post_processing_files, + dpf_integration_helpers, get_directions_plotter, - get_dpf_unit_system, launch_acp, material_property_sets, print_model, @@ -70,7 +68,7 @@ # Launch PyACP # ------------ # -# Get the example file from the server. +# Download the example input file. tempdir = tempfile.TemporaryDirectory() WORKING_DIR = pathlib.Path(tempdir.name) input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_DAT, WORKING_DIR) @@ -83,18 +81,9 @@ # Create an ACP workflow instance and load the model # -------------------------------------------------- # -# Define the input file and instantiate an ``ACPWorkflow`` instance. -# The ``ACPWorkflow`` class provides convenience methods that simplify the file handling. -# It automatically creates a model based on the input file. - -workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp, - cdb_or_dat_file_path=input_file, - local_working_directory=WORKING_DIR, -) +# Import the model from the input file. -model = workflow.model -print(workflow.working_directory.path) +model = acp.import_model(input_file, format="ansys:dat") print(model.unit_system) # %% @@ -204,7 +193,9 @@ # %% # Load the CDB file into PyMAPDL. -mapdl.input(str(workflow.get_local_cdb_file())) +analysis_model_path = WORKING_DIR / "analysis_model.cdb" +model.export_analysis_model(analysis_model_path) +mapdl.input(str(analysis_model_path)) # %% # Solve the model. @@ -221,8 +212,8 @@ # %% # Download the RST file for composite-specific postprocessing. rstfile_name = f"{mapdl.jobname}.rst" -rst_file_local_path = workflow.working_directory.path / rstfile_name -mapdl.download(rstfile_name, str(workflow.working_directory.path)) +rst_file_local_path = WORKING_DIR / rstfile_name +mapdl.download(rstfile_name, str(WORKING_DIR)) # %% # Postprocessing with PyDPF Composites @@ -233,6 +224,10 @@ from ansys.dpf.composites.composite_model import CompositeModel from ansys.dpf.composites.constants import FailureOutput +from ansys.dpf.composites.data_sources import ( + CompositeDefinitionFiles, + ContinuousFiberCompositesFiles, +) from ansys.dpf.composites.failure_criteria import CombinedFailureCriterion, MaxStrainCriterion from ansys.dpf.composites.server_helpers import connect_to_or_start_server @@ -252,9 +247,17 @@ # %% # Create the composite model and configure its input. +composite_definitions_file = WORKING_DIR / "ACPCompositeDefinitions.h5" +model.export_shell_composite_definitions(composite_definitions_file) +materials_file = WORKING_DIR / "materials.xml" +model.export_materials(materials_file) composite_model = CompositeModel( - get_composite_post_processing_files(workflow, rst_file_local_path), - default_unit_system=get_dpf_unit_system(model.unit_system), + composite_files=ContinuousFiberCompositesFiles( + rst=rst_file_local_path, + composite={"shell": CompositeDefinitionFiles(composite_definitions_file)}, + engineering_data=materials_file, + ), + default_unit_system=dpf_integration_helpers.get_dpf_unit_system(model.unit_system), server=dpf_server, ) diff --git a/examples/workflows/02-advanced-pymapdl-workflow.py b/examples/workflows/02-advanced-pymapdl-workflow.py index a25a7c2a73..f328c0d317 100644 --- a/examples/workflows/02-advanced-pymapdl-workflow.py +++ b/examples/workflows/02-advanced-pymapdl-workflow.py @@ -77,15 +77,9 @@ # Load mesh and materials from CDB file # ------------------------------------- -# %% -# Send the input file to the ACP server. -cdb_file_path = acp.upload_file(local_path=input_file) - # %% # Load the CDB file into PyACP and set the unit system. -model = acp.import_model( - path=cdb_file_path, format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA -) +model = acp.import_model(path=input_file, format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA) model @@ -290,34 +284,18 @@ def add_ply(mg, name, ply_material, angle, oss): composite_definition_h5_filename = "ACPCompositeDefinitions.h5" matml_filename = "materials.xml" -if acp.is_remote: - export_path = pathlib.PurePosixPath(".") -else: - export_path = working_dir_path # type: ignore - # %% # Update and save the ACP model. model.update() -model.save(export_path / acph5_filename, save_cache=True) +model.save(working_dir_path / acph5_filename, save_cache=True) # %% # Save the model as a CDB file for solving with PyMAPDL. -model.export_analysis_model(export_path / cdb_filename_out) +model.export_analysis_model(working_dir_path / cdb_filename_out) # Export the shell lay-up and material file for PyDPF Composites. -model.export_shell_composite_definitions(export_path / composite_definition_h5_filename) -model.export_materials(export_path / matml_filename) - -# %% -# Download files from the ACP server to a local directory. -for filename in [ - acph5_filename, - cdb_filename_out, - composite_definition_h5_filename, - matml_filename, -]: - acp.download_file( - remote_filename=export_path / filename, local_path=working_dir_path / filename - ) +model.export_shell_composite_definitions(working_dir_path / composite_definition_h5_filename) +model.export_materials(working_dir_path / matml_filename) + # %% # Solve with PyMAPDL diff --git a/examples/workflows/03-pymechanical-shell-workflow.py b/examples/workflows/03-pymechanical-shell-workflow.py index b779645ebe..faf7997182 100644 --- a/examples/workflows/03-pymechanical-shell-workflow.py +++ b/examples/workflows/03-pymechanical-shell-workflow.py @@ -141,9 +141,7 @@ matml_file = "materials.xml" # TODO: load an example materials XML file instead of defining the materials in ACP -mesh_path = acp.upload_file(mesh_path) - -model = acp.import_model(path=mesh_path, format="ansys:h5") +model = acp.import_model(mesh_path, format="ansys:h5") mat = model.create_material(name="mat") @@ -206,20 +204,8 @@ model.update() -if acp.is_remote: - export_path = pathlib.PurePosixPath(".") - -else: - export_path = working_dir_path # type: ignore - -model.export_shell_composite_definitions(export_path / composite_definitions_h5) -model.export_materials(export_path / matml_file) - -for filename in [ - composite_definitions_h5, - matml_file, -]: - acp.download_file(export_path / filename, working_dir_path / filename) +model.export_shell_composite_definitions(working_dir_path / composite_definitions_h5) +model.export_materials(working_dir_path / matml_file) # %% # Import materials and plies into Mechanical diff --git a/examples/workflows/04-pymechanical-solid-workflow.py b/examples/workflows/04-pymechanical-solid-workflow.py index 47b20f762d..78fe3785c5 100644 --- a/examples/workflows/04-pymechanical-solid-workflow.py +++ b/examples/workflows/04-pymechanical-solid-workflow.py @@ -184,9 +184,6 @@ solid_model_cdb_file = "SolidModel.cdb" solid_model_composite_definitions_h5 = "SolidModel.h5" - -mesh_path = acp.upload_file(mesh_path) - model = acp.import_model(path=mesh_path, format="ansys:h5") mat = model.create_material(name="mat") @@ -254,22 +251,9 @@ model.update() -if acp.is_remote: - export_path = pathlib.PurePosixPath(".") - -else: - export_path = working_dir_path # type: ignore - -model.export_materials(export_path / matml_file) -solid_model.export(export_path / solid_model_cdb_file, format="ansys:cdb") -solid_model.export(export_path / solid_model_composite_definitions_h5, format="ansys:h5") - -for filename in [ - matml_file, - solid_model_cdb_file, - solid_model_composite_definitions_h5, -]: - acp.download_file(export_path / filename, working_dir_path / filename) +model.export_materials(working_dir_path / matml_file) +solid_model.export(working_dir_path / solid_model_cdb_file, format="ansys:cdb") +solid_model.export(working_dir_path / solid_model_composite_definitions_h5, format="ansys:h5") # %% diff --git a/examples/workflows/05-pymechanical-to-cdb-workflow.py b/examples/workflows/05-pymechanical-to-cdb-workflow.py index 17a4029df2..5df0e4535e 100644 --- a/examples/workflows/05-pymechanical-to-cdb-workflow.py +++ b/examples/workflows/05-pymechanical-to-cdb-workflow.py @@ -171,7 +171,7 @@ # Setup basic ACP lay-up based on the CDB file. -model = acp.import_model(path=acp.upload_file(cdb_path_initial), format="ansys:cdb") +model = acp.import_model(path=cdb_path_initial, format="ansys:cdb") mat = model.create_material(name="mat") @@ -234,22 +234,13 @@ model.update() -if acp.is_remote: - export_path = pathlib.PurePosixPath(".") - -else: - export_path = working_dir_path # type: ignore - cdb_filename_out = "model_from_acp.cdb" composite_definitions_h5_filename = "ACPCompositeDefinitions.h5" matml_filename = "materials.xml" -model.export_analysis_model(export_path / cdb_filename_out) -model.export_shell_composite_definitions(export_path / composite_definitions_h5_filename) -model.export_materials(export_path / matml_filename) - -for filename in [cdb_filename_out, composite_definitions_h5_filename, matml_filename]: - acp.download_file(export_path / filename, working_dir_path / filename) +model.export_analysis_model(working_dir_path / cdb_filename_out) +model.export_shell_composite_definitions(working_dir_path / composite_definitions_h5_filename) +model.export_materials(working_dir_path / matml_filename) # %% # Solve with PyMAPDL diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 0407a519e6..63186aacf7 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -27,7 +27,13 @@ import importlib.metadata -from . import extras, material_property_sets, mechanical_integration_helpers, mesh_data +from . import ( + dpf_integration_helpers, + extras, + material_property_sets, + mechanical_integration_helpers, + mesh_data, +) from ._model_printer import get_model_tree, print_model from ._plotter import get_directions_plotter from ._recursive_copy import LinkedObjectHandling, recursive_copy @@ -150,7 +156,6 @@ VirtualGeometry, VirtualGeometryDimension, ) -from ._workflow import ACPWorkflow, get_composite_post_processing_files, get_dpf_unit_system __version__ = importlib.metadata.version(__name__.replace(".", "-")) @@ -158,7 +163,6 @@ __all__ = [ "__version__", "ACPInstance", - "ACPWorkflow", "AnalysisPly", "ArrowType", "BaseElementMaterialHandling", @@ -200,9 +204,7 @@ "FieldDefinition", "GeometricalRuleType", "GeometricalSelectionRule", - "get_composite_post_processing_files", "get_directions_plotter", - "get_dpf_unit_system", "get_model_tree", "HDF5CompositeCAEImportMode", "HDF5CompositeCAEProjectionMode", @@ -234,6 +236,7 @@ "material_property_sets", "Material", "mechanical_integration_helpers", + "dpf_integration_helpers", "MeshImportType", "Model", "ModelingGroup", diff --git a/src/ansys/acp/core/_server/acp_instance.py b/src/ansys/acp/core/_server/acp_instance.py index fe4c8716ce..bbcf848281 100644 --- a/src/ansys/acp/core/_server/acp_instance.py +++ b/src/ansys/acp/core/_server/acp_instance.py @@ -25,6 +25,7 @@ import os import pathlib import shutil +import typing from typing import Any, Generic, Protocol, TypeVar, cast import grpc @@ -33,50 +34,128 @@ from ansys.api.acp.v0.base_pb2 import CollectionPath, DeleteRequest, Empty, ListRequest from ansys.tools.filetransfer import Client as FileTransferClient -from .._tree_objects import Model from .._tree_objects._grpc_helpers.exceptions import wrap_grpc_errors -from .._tree_objects.base import ServerWrapper from .._utils.typing_helper import PATH as _PATH from .common import ServerProtocol +if typing.TYPE_CHECKING: # pragma: no cover + from .._tree_objects import Model + + __all__ = ["ACPInstance"] -class FiletransferStrategy(Protocol): +class FileTransferStrategy(Protocol): def upload_file(self, local_path: _PATH) -> pathlib.PurePath: ... - def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: ... + def download_file(self, remote_path: _PATH, local_path: _PATH) -> None: ... + + def to_export_path(self, path: _PATH) -> _PATH: ... + +class LocalFileTransferStrategy(FileTransferStrategy): + def __init__(self, working_directory: _PATH) -> None: + self._working_directory = pathlib.Path(working_directory) -class LocalFileTransferStrategy(FiletransferStrategy): def upload_file(self, local_path: _PATH) -> pathlib.Path: - return pathlib.Path(local_path) + return self._get_remote_path(local_path) - def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: - # TODO: improve the distinction between remote filename and remote path - remote_filename = pathlib.Path(remote_filename) + def download_file(self, remote_path: _PATH, local_path: _PATH) -> None: + remote_path_aslocal = self._get_local_path(remote_path) local_filename = pathlib.Path(local_path) - if local_filename.exists() and local_filename.samefile(remote_filename): + if local_filename.exists() and local_filename.samefile(remote_path_aslocal): return - shutil.copyfile(remote_filename, local_path) + shutil.copyfile(remote_path_aslocal, local_path) + + def to_export_path(self, path: _PATH) -> _PATH: + return self._get_remote_path(path) + + def _get_remote_path(self, path: _PATH) -> pathlib.Path: + """Get the path in the format understood by the server. + + Convert the path from the format understood by the Python client to + the format understood by the server. + """ + return self._get_path_impl(path, pathlib.Path().cwd(), self._working_directory) + + def _get_local_path(self, path: _PATH) -> pathlib.Path: + """Get the path in the format understood by the Python client. + Convert the path from the format understood by the server to + the format understood by the Python client. + """ + return self._get_path_impl(path, self._working_directory, pathlib.Path().cwd()) -class RemoteFileTransferStrategy(FiletransferStrategy): + @staticmethod + def _get_path_impl( + path: _PATH, + initial_working_directory: pathlib.Path, + target_working_directory: pathlib.Path, + ) -> pathlib.Path: + path = pathlib.Path(path) + if path.is_absolute() or initial_working_directory == target_working_directory: + return path + path = (initial_working_directory / path).resolve() + try: + return path.relative_to(target_working_directory) + except ValueError: + # If the path cannot be made relative (e.g. since it is on a different drive), + # return the absolute path. + return path + + +class RemoteFileTransferStrategy(FileTransferStrategy): _ft_client: FileTransferClient def __init__(self, channel: grpc.Channel) -> None: self._ft_client = FileTransferClient(channel) def upload_file(self, local_path: _PATH) -> pathlib.PurePath: - remote_filename = os.path.basename(local_path) - self._ft_client.upload_file(local_filename=str(local_path), remote_filename=remote_filename) - return pathlib.PurePosixPath(remote_filename) + remote_path = os.path.basename(local_path) + self._ft_client.upload_file(local_filename=str(local_path), remote_filename=remote_path) + return pathlib.PurePosixPath(remote_path) - def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: + def download_file(self, remote_path: _PATH, local_path: _PATH) -> None: self._ft_client.download_file( - remote_filename=str(remote_filename), local_filename=str(local_path) + remote_filename=str(remote_path), local_filename=str(local_path) ) + def to_export_path(self, path: _PATH) -> _PATH: + # Export to the working directory of the server + return pathlib.Path(path).name + + +class FileTransferHandler: + def __init__( + self, filetransfer_strategy: FileTransferStrategy, auto_transfer_files: bool + ) -> None: + self._filetransfer_strategy = filetransfer_strategy + self._auto_transfer_files = auto_transfer_files + + def upload_file_if_autotransfer(self, local_path: _PATH) -> pathlib.PurePath: + if self._auto_transfer_files: + return self.upload_file(local_path) + return pathlib.Path(local_path) + + def download_file_if_autotransfer(self, remote_path: _PATH, local_path: _PATH) -> None: + if self._auto_transfer_files: + self.download_file(remote_path, local_path) + else: + # If auto-transfer is disabled, the export path should be the + # same as the local path. Otherwise, this is a bug in our code. + assert remote_path == local_path + + def to_export_path(self, path: _PATH) -> _PATH: + if self._auto_transfer_files: + return self._filetransfer_strategy.to_export_path(path) + return path + + def upload_file(self, local_path: _PATH) -> pathlib.PurePath: + return self._filetransfer_strategy.upload_file(local_path) + + def download_file(self, remote_path: _PATH, local_path: _PATH) -> None: + self._filetransfer_strategy.download_file(remote_path, local_path) + ServerT = TypeVar("ServerT", bound=ServerProtocol, covariant=True) @@ -98,7 +177,7 @@ class ACPInstance(Generic[ServerT]): """ _server: ServerT - _filetransfer_strategy: FiletransferStrategy + _filetransfer_handler: FileTransferHandler _channel: grpc.Channel _is_remote: bool @@ -107,12 +186,12 @@ def __init__( *, server: ServerT, channel: grpc.Channel, - filetransfer_strategy: FiletransferStrategy, + filetransfer_handler: FileTransferHandler, is_remote: bool, ) -> None: self._server = server self._channel = channel - self._filetransfer_strategy = filetransfer_strategy + self._filetransfer_handler = filetransfer_handler self._is_remote = is_remote @property @@ -172,6 +251,9 @@ def import_model( : The loaded ``Model`` instance. """ + from .._tree_objects import Model + from .._tree_objects.base import ServerWrapper + server_wrapper = ServerWrapper.from_acp_instance(self) if format == "acp:h5": if kwargs: @@ -197,6 +279,8 @@ def clear(self) -> None: Closes the models which are currently open, without first saving them to a file. """ + from .._tree_objects import Model + model_stub = model_pb2_grpc.ObjectServiceStub(self._channel) with wrap_grpc_errors(): for model in model_stub.List( @@ -214,6 +298,8 @@ def models(self) -> tuple[Model, ...]: Note that the models are listed in arbitrary order. """ + from .._tree_objects import Model + model_stub = model_pb2_grpc.ObjectServiceStub(self._channel) return tuple( [ @@ -243,9 +329,9 @@ def upload_file(self, local_path: _PATH) -> pathlib.PurePath: : The path of the uploaded file on the server. """ - return self._filetransfer_strategy.upload_file(local_path) + return self._filetransfer_handler.upload_file(local_path) - def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: + def download_file(self, remote_path: _PATH, local_path: _PATH) -> None: """Download a file from the server. .. warning:: @@ -256,12 +342,12 @@ def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: Parameters ---------- - remote_filename : + remote_path : The path of the file on the server. local_path : The path of the file to be downloaded to. """ - self._filetransfer_strategy.download_file(remote_filename, local_path) + self._filetransfer_handler.download_file(remote_path, local_path) def check(self, timeout: float | None = None) -> bool: """Check if the ACP instance is running. diff --git a/src/ansys/acp/core/_server/launch.py b/src/ansys/acp/core/_server/launch.py index f78048829f..16e1287423 100644 --- a/src/ansys/acp/core/_server/launch.py +++ b/src/ansys/acp/core/_server/launch.py @@ -22,6 +22,8 @@ from __future__ import annotations +import os + from packaging import version from ansys.tools.local_product_launcher.config import get_launch_mode_for @@ -30,7 +32,8 @@ from .acp_instance import ( ACPInstance, - FiletransferStrategy, + FileTransferHandler, + FileTransferStrategy, LocalFileTransferStrategy, RemoteFileTransferStrategy, ) @@ -45,6 +48,7 @@ def launch_acp( config: DirectLaunchConfig | DockerComposeLaunchConfig | None = None, launch_mode: LaunchMode | None = None, timeout: float | None = 30.0, + auto_transfer_files: bool = True, ) -> ACPInstance[ControllableServerProtocol]: """Launch an ACP instance. @@ -62,12 +66,23 @@ def launch_acp( The configuration used for launching ACP. If unspecified, the default for the given launch mode is used. launch_mode : - Specifies which ACP launcher is used. One of ``direct`` or - ``docker_compose``. If unspecified, the configured default is - used. If no default is configured, ``direct`` is used. + Specifies which ACP launcher is used. One of ``direct``, + ``docker_compose``, or ``connect``. If unspecified, the + configured default is used. If no default is configured, + ``direct`` is used. timeout : Timeout to wait until ACP responds. If ``None`` is specified, the check that ACP has started is skipped. + auto_transfer_files : + Determines whether input and output files are automatically + transferred (up- or downloaded) to the server. If ``True``, + files are automatically transferred, and all paths in the + import or export methods are *local* paths. If ``False``, + file transfer needs to be handled manually, and the paths + are relative to the server working directory. + If the ``launch_mode`` is ``"direct"``, this only has an + effect if the current working directory is changed after + launching the server. Returns ------- @@ -81,16 +96,11 @@ def launch_acp( ) # The fallback launch mode for ACP is the direct launch mode. if launch_mode_evaluated in (LaunchMode.DIRECT, FALLBACK_LAUNCH_MODE_NAME): - filetransfer_strategy: FiletransferStrategy = LocalFileTransferStrategy() + filetransfer_strategy: FileTransferStrategy = LocalFileTransferStrategy(os.getcwd()) is_remote = False - elif launch_mode_evaluated == LaunchMode.DOCKER_COMPOSE: - filetransfer_strategy = RemoteFileTransferStrategy( - channel=server_instance.channels[ServerKey.FILE_TRANSFER] - ) - is_remote = True - elif launch_mode_evaluated == LaunchMode.CONNECT: + elif launch_mode_evaluated in (LaunchMode.DOCKER_COMPOSE, LaunchMode.CONNECT): filetransfer_strategy = RemoteFileTransferStrategy( - channel=server_instance.channels[ServerKey.FILE_TRANSFER] + channel=server_instance.channels[ServerKey.FILE_TRANSFER], ) is_remote = True else: @@ -98,7 +108,9 @@ def launch_acp( acp = ACPInstance( server=server_instance, - filetransfer_strategy=filetransfer_strategy, + filetransfer_handler=FileTransferHandler( + filetransfer_strategy, auto_transfer_files=auto_transfer_files + ), channel=server_instance.channels[ServerKey.MAIN], is_remote=is_remote, ) diff --git a/src/ansys/acp/core/_tree_objects/_solid_model_export.py b/src/ansys/acp/core/_tree_objects/_solid_model_export.py index cde63b9e6d..328d1bb7bc 100644 --- a/src/ansys/acp/core/_tree_objects/_solid_model_export.py +++ b/src/ansys/acp/core/_tree_objects/_solid_model_export.py @@ -26,7 +26,6 @@ from ansys.api.acp.v0 import solid_model_export_pb2 -from .._utils.path_to_str import path_to_str_checked from .._utils.typing_helper import PATH as _PATH from ._grpc_helpers.exceptions import wrap_grpc_errors from .base import CreatableTreeObject @@ -55,14 +54,15 @@ def export(self, path: _PATH, *, format: SolidModelExportFormat) -> None: and ``"ansys:cdb"``. """ - with wrap_grpc_errors(): - self._get_stub().ExportToFile( # type: ignore - solid_model_export_pb2.ExportToFileRequest( - resource_path=self._resource_path, - path=path_to_str_checked(path), - format=typing.cast(typing.Any, solid_model_export_format_to_pb(format)), + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + self._get_stub().ExportToFile( # type: ignore + solid_model_export_pb2.ExportToFileRequest( + resource_path=self._resource_path, + path=export_path, + format=typing.cast(typing.Any, solid_model_export_format_to_pb(format)), + ) ) - ) def export_skin(self, path: _PATH, *, format: SolidModelSkinExportFormat) -> None: """Export the skin of the solid model to a file. @@ -76,11 +76,14 @@ def export_skin(self, path: _PATH, *, format: SolidModelSkinExportFormat) -> Non ``"step"``, ``"iges"``, and ``"stl"``. """ - with wrap_grpc_errors(): - self._get_stub().ExportSkin( # type: ignore - solid_model_export_pb2.ExportSkinRequest( - resource_path=self._resource_path, - path=path_to_str_checked(path), - format=typing.cast(typing.Any, solid_model_skin_export_format_to_pb(format)), + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + self._get_stub().ExportSkin( # type: ignore + solid_model_export_pb2.ExportSkinRequest( + resource_path=self._resource_path, + path=export_path, + format=typing.cast( + typing.Any, solid_model_skin_export_format_to_pb(format) + ), + ) ) - ) diff --git a/src/ansys/acp/core/_tree_objects/base.py b/src/ansys/acp/core/_tree_objects/base.py index cf232eee2b..08da2df912 100644 --- a/src/ansys/acp/core/_tree_objects/base.py +++ b/src/ansys/acp/core/_tree_objects/base.py @@ -24,7 +24,8 @@ from __future__ import annotations from abc import abstractmethod -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Iterator +import contextlib from dataclasses import dataclass import typing from typing import Any, Generic, TypeVar, cast @@ -36,10 +37,13 @@ from ansys.api.acp.v0.base_pb2 import CollectionPath, DeleteRequest, GetRequest, ResourcePath +from .._server.acp_instance import ACPInstance, FileTransferHandler +from .._utils.path_to_str import path_to_str_checked from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty from .._utils.resource_paths import common_path from .._utils.resource_paths import join as _rp_join from .._utils.resource_paths import to_parts +from .._utils.typing_helper import PATH from ._grpc_helpers.exceptions import wrap_grpc_errors from ._grpc_helpers.linked_object_helpers import get_linked_paths, unlink_objects from ._grpc_helpers.polymorphic_from_pb import ( @@ -65,9 +69,6 @@ ) from ._object_cache import ObjectCacheMixin, constructor_with_cache -if typing.TYPE_CHECKING: # pragma: no cover - from .._server import ACPInstance - @mark_grpc_properties class TreeObjectBase(ObjectCacheMixin, GrpcObjectBase): @@ -194,14 +195,34 @@ class ServerWrapper: channel: Channel version: Version + filetransfer_handler: FileTransferHandler @classmethod def from_acp_instance(cls, acp_instance: ACPInstance[Any]) -> ServerWrapper: """Convert an ACP instance into the wrapper needed by tree objects.""" return cls( - channel=acp_instance._channel, version=parse_version(acp_instance.server_version) + channel=acp_instance._channel, + version=parse_version(acp_instance.server_version), + filetransfer_handler=acp_instance._filetransfer_handler, ) + def auto_upload(self, local_path: PATH | None, allow_none: bool = False) -> str: + """Handle auto-transfer of a file to the server.""" + if local_path is None: + if allow_none: + return "" + raise TypeError("Expected a Path or str, not 'None'.") + return path_to_str_checked( + self.filetransfer_handler.upload_file_if_autotransfer(local_path) + ) + + @contextlib.contextmanager + def auto_download(self, local_path: PATH) -> Iterator[str]: + """Handle auto-transfer of a file from the server.""" + export_path = self.filetransfer_handler.to_export_path(local_path) + yield path_to_str_checked(export_path) + self.filetransfer_handler.download_file_if_autotransfer(export_path, local_path) + class StubStore(Generic[StubT]): """Stores a gRPC stub, and creates it on demand.""" diff --git a/src/ansys/acp/core/_tree_objects/cad_geometry.py b/src/ansys/acp/core/_tree_objects/cad_geometry.py index e5512130a1..04762d901a 100644 --- a/src/ansys/acp/core/_tree_objects/cad_geometry.py +++ b/src/ansys/acp/core/_tree_objects/cad_geometry.py @@ -179,8 +179,9 @@ def visualization_mesh(self) -> TriangleMesh: requires_uptodate=True, ) - def refresh(self) -> None: + def refresh(self, path: PATH) -> None: """Reload the geometry from its external source.""" + self.external_path = self._server_wrapper.auto_upload(path) stub = cast(cad_geometry_pb2_grpc.ObjectServiceStub, self._get_stub()) with wrap_grpc_errors(): stub.Refresh( diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index c5b3075d6f..bca3e1ec96 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -354,8 +354,9 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: layup_mapping_object_pb2_grpc.ObjectServiceStub, ) - def refresh(self) -> None: + def refresh(self, path: _PATH) -> None: """Re-import the solid model from the external file.""" + self.external_path = self._server_wrapper.auto_upload(path) with wrap_grpc_errors(): self._get_stub().Refresh( # type: ignore imported_solid_model_pb2.RefreshRequest(resource_path=self._resource_path) diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 0c2282bdd1..947dd24d73 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -67,7 +67,6 @@ ) from ansys.api.acp.v0.base_pb2 import CollectionPath -from .._utils.path_to_str import path_to_str_checked from .._utils.property_protocols import ReadOnlyProperty, ReadWriteProperty from .._utils.resource_paths import join as rp_join from .._utils.typing_helper import PATH as _PATH @@ -321,9 +320,7 @@ def _from_file(cls, *, path: _PATH, server_wrapper: ServerWrapper) -> Model: server_wrapper: Representation of the ACP instance. """ - # Send absolute paths to the server, since its CWD may not match - # the Python CWD. - request = model_pb2.LoadFromFileRequest(path=path_to_str_checked(path)) + request = model_pb2.LoadFromFileRequest(path=server_wrapper.auto_upload(path)) with wrap_grpc_errors(): reply = model_pb2_grpc.ObjectServiceStub(server_wrapper.channel).LoadFromFile(request) return cls._from_object_info(object_info=reply, server_wrapper=server_wrapper) @@ -368,7 +365,7 @@ def _from_fe_file( ignored_entities_pb = [ignorable_entity_to_pb(val) for val in ignored_entities] request = model_pb2.LoadFromFEFileRequest( - path=path_to_str_checked(path), + path=server_wrapper.auto_upload(path), format=cast(Any, format_pb), ignored_entities=cast(Any, ignored_entities_pb), convert_section_data=convert_section_data, @@ -404,14 +401,15 @@ def save(self, path: _PATH, *, save_cache: bool = True) -> None: save_cache: Whether to store the update results such as Analysis Plies and solid models. """ - with wrap_grpc_errors(): - self._get_stub().SaveToFile( - model_pb2.SaveToFileRequest( - resource_path=self._resource_path, - path=path_to_str_checked(path), - save_cache=save_cache, + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + self._get_stub().SaveToFile( + model_pb2.SaveToFileRequest( + resource_path=self._resource_path, + path=export_path, + save_cache=save_cache, + ) ) - ) def export_analysis_model(self, path: _PATH) -> None: """Save the analysis model to a CDB file. @@ -421,13 +419,14 @@ def export_analysis_model(self, path: _PATH) -> None: path: Target file path. E.g. /tmp/ACPAnalysisModel.cdb """ - with wrap_grpc_errors(): - self._get_stub().SaveAnalysisModel( - model_pb2.SaveAnalysisModelRequest( - resource_path=self._resource_path, - path=path_to_str_checked(path), + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + self._get_stub().SaveAnalysisModel( + model_pb2.SaveAnalysisModelRequest( + resource_path=self._resource_path, + path=export_path, + ) ) - ) @supported_since("25.1") def export_hdf5_composite_cae( @@ -475,21 +474,22 @@ def export_hdf5_composite_cae( file. This may be needed for compatibility with programs that don't fully support unicode when reading the file. """ - with wrap_grpc_errors(): - self._get_stub().ExportHDF5CompositeCAE( - model_pb2.ExportHDF5CompositeCAERequest( - resource_path=self._resource_path, - path=path_to_str_checked(path), - remove_midside_nodes=remove_midside_nodes, - layup_representation_3d=layup_representation_3d, - offset_type=offset_type_to_pb(offset_type), # type: ignore - ascii_encoding=ascii_encoding, - all_elements=all_elements, - element_sets=[element_set._resource_path for element_set in element_sets], - all_plies=all_plies, - plies=[ply._resource_path for ply in plies], + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + self._get_stub().ExportHDF5CompositeCAE( + model_pb2.ExportHDF5CompositeCAERequest( + resource_path=self._resource_path, + path=export_path, + remove_midside_nodes=remove_midside_nodes, + layup_representation_3d=layup_representation_3d, + offset_type=offset_type_to_pb(offset_type), # type: ignore + ascii_encoding=ascii_encoding, + all_elements=all_elements, + element_sets=[element_set._resource_path for element_set in element_sets], + all_plies=all_plies, + plies=[ply._resource_path for ply in plies], + ) ) - ) @supported_since("25.1") def import_hdf5_composite_cae( @@ -560,7 +560,7 @@ def import_hdf5_composite_cae( self._get_stub().ImportHDF5CompositeCAE( model_pb2.ImportHDF5CompositeCAERequest( resource_path=self._resource_path, - path=path_to_str_checked(path), + path=self._server_wrapper.auto_upload(path), import_mode=hdf5_composite_cae_import_mode_to_pb(import_mode), # type: ignore projection_mode=hdf5_composite_cae_projection_mode_to_pb(projection_mode), # type: ignore minimum_angle_tolerance=minimum_angle_tolerance, @@ -581,12 +581,13 @@ def export_shell_composite_definitions(self, path: _PATH) -> None: path: File path. Eg. /tmp/ACPCompositeDefinitions.h5 """ - with wrap_grpc_errors(): - self._get_stub().SaveShellCompositeDefinitions( - model_pb2.SaveShellCompositeDefinitionsRequest( - resource_path=self._resource_path, path=path_to_str_checked(path) + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + self._get_stub().SaveShellCompositeDefinitions( + model_pb2.SaveShellCompositeDefinitionsRequest( + resource_path=self._resource_path, path=export_path + ) ) - ) @supported_since("25.1") def import_materials( @@ -616,15 +617,14 @@ def import_materials( collection_path = CollectionPath( value=rp_join(self._resource_path.value, Material._COLLECTION_LABEL) ) + with wrap_grpc_errors(): material_stub.ImportMaterialFiles( material_pb2.ImportMaterialFilesRequest( collection_path=collection_path, - matml_path=path_to_str_checked(matml_path), - material_apdl_path=( - path_to_str_checked(material_apdl_path) - if material_apdl_path is not None - else "" + matml_path=self._server_wrapper.auto_upload(matml_path), + material_apdl_path=self._server_wrapper.auto_upload( + material_apdl_path, allow_none=True ), ) ) @@ -645,14 +645,15 @@ def export_materials(self, path: _PATH) -> None: collection_path = CollectionPath( value=rp_join(self._resource_path.value, Material._COLLECTION_LABEL) ) - with wrap_grpc_errors(): - material_stub.SaveToFile( - material_pb2.SaveToFileRequest( - collection_path=collection_path, - path=path_to_str_checked(path), - format=material_pb2.SaveToFileRequest.ANSYS_XML, + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + material_stub.SaveToFile( + material_pb2.SaveToFileRequest( + collection_path=collection_path, + path=export_path, + format=material_pb2.SaveToFileRequest.ANSYS_XML, + ) ) - ) @supported_since("25.1") def export_modeling_ply_geometries( @@ -714,23 +715,26 @@ def export_modeling_ply_geometries( if arrow_length is None: arrow_length = np.sqrt(self.average_element_size) - with wrap_grpc_errors(): - modeling_ply_stub.ExportGeometries( - ply_geometry_export_pb2.ExportGeometriesRequest( - path=path_to_str_checked(path), - plies=mp_resource_paths, - options=ply_geometry_export_pb2.ExportOptions( - format=typing.cast(typing.Any, ply_geometry_export_format_to_pb(format)), - offset_type=typing.cast(typing.Any, offset_type_to_pb(offset_type)), - include_surface=include_surface, - include_boundary=include_boundary, - include_first_material_direction=include_first_material_direction, - include_second_material_direction=include_second_material_direction, - arrow_length=arrow_length, - arrow_type=typing.cast(typing.Any, arrow_type_to_pb(arrow_type)), - ), + with self._server_wrapper.auto_download(path) as export_path: + with wrap_grpc_errors(): + modeling_ply_stub.ExportGeometries( + ply_geometry_export_pb2.ExportGeometriesRequest( + path=export_path, + plies=mp_resource_paths, + options=ply_geometry_export_pb2.ExportOptions( + format=typing.cast( + typing.Any, ply_geometry_export_format_to_pb(format) + ), + offset_type=typing.cast(typing.Any, offset_type_to_pb(offset_type)), + include_surface=include_surface, + include_boundary=include_boundary, + include_first_material_direction=include_first_material_direction, + include_second_material_direction=include_second_material_direction, + arrow_length=arrow_length, + arrow_type=typing.cast(typing.Any, arrow_type_to_pb(arrow_type)), + ), + ) ) - ) create_material = define_create_method( Material, func_name="create_material", parent_class_name="Model", module_name=__module__ diff --git a/src/ansys/acp/core/_workflow.py b/src/ansys/acp/core/_workflow.py deleted file mode 100644 index 63a0c06331..0000000000 --- a/src/ansys/acp/core/_workflow.py +++ /dev/null @@ -1,446 +0,0 @@ -# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from collections.abc import Callable -import pathlib -import shutil -import tempfile -import typing -from typing import Any, Protocol - -from . import CADGeometry, UnitSystemType -from ._server import ACPInstance -from ._server.common import ServerProtocol -from ._tree_objects import Model -from ._utils.typing_helper import PATH - -# Avoid dependencies on pydpf-composites and dpf-core if it is not used -if typing.TYPE_CHECKING: # pragma: no cover - from ansys.dpf.composites.data_sources import ContinuousFiberCompositesFiles - from ansys.dpf.core import UnitSystem - -__all__ = ["ACPWorkflow", "get_composite_post_processing_files", "get_dpf_unit_system"] - - -class _LocalWorkingDir: - def __init__(self, path: PATH | None = None): - self._user_defined_working_dir = None - self._temp_working_dir = None - if path is None: - self._temp_working_dir = tempfile.TemporaryDirectory() - else: - self._user_defined_working_dir = pathlib.Path(path) - - @property - def path(self) -> pathlib.Path: - if self._user_defined_working_dir is not None: - return self._user_defined_working_dir - else: - # Make typechecker happy - assert self._temp_working_dir is not None - return pathlib.Path(self._temp_working_dir.name) - - -class _FileStrategy(Protocol): - def get_file( - self, get_file_callable: Callable[[pathlib.Path], None], filename: str - ) -> pathlib.Path: ... - - def copy_input_file_to_local_workdir(self, path: pathlib.Path) -> pathlib.Path: ... - - def upload_input_file_to_server(self, path: pathlib.Path) -> pathlib.PurePath: ... - - -def _copy_file_workdir(path: pathlib.Path, working_directory: pathlib.Path) -> pathlib.Path: - try: - shutil.copy(path, working_directory) - except shutil.SameFileError: - pass - return working_directory / path.name - - -class _LocalFileTransferStrategy: - """File transfer strategy for local workflows. - - Save output files to the local working directory and do nothing for input files. - """ - - def __init__(self, local_working_directory: _LocalWorkingDir): - self._local_working_directory = local_working_directory - - def get_file( - self, get_file_callable: Callable[[pathlib.Path], None], filename: str - ) -> pathlib.Path: - local_path = self._local_working_directory.path / filename - get_file_callable(self._local_working_directory.path / filename) - return local_path - - def copy_input_file_to_local_workdir(self, path: pathlib.Path) -> pathlib.Path: - return _copy_file_workdir(path=path, working_directory=self._local_working_directory.path) - - def upload_input_file_to_server(self, path: pathlib.Path) -> pathlib.PurePath: - return path - - -class _RemoteFileTransferStrategy: - """File transfer strategy for remote workflows. - - Download output files from the server to the local working directory and upload - input files to the server. - """ - - def __init__(self, local_working_directory: _LocalWorkingDir, acp: ACPInstance[ServerProtocol]): - self._local_working_directory = local_working_directory - self._acp_instance = acp - - def get_file( - self, get_file_callable: Callable[[pathlib.Path], None], filename: str - ) -> pathlib.Path: - get_file_callable(pathlib.Path(filename)) - local_path = self._local_working_directory.path / filename - self._acp_instance.download_file(remote_filename=filename, local_path=str(local_path)) - return local_path - - def copy_input_file_to_local_workdir(self, path: pathlib.Path) -> pathlib.Path: - return _copy_file_workdir(path=path, working_directory=self._local_working_directory.path) - - def upload_input_file_to_server(self, path: pathlib.Path) -> pathlib.PurePath: - return self._acp_instance.upload_file(local_path=path) - - -def _get_file_transfer_strategy( - acp: ACPInstance[ServerProtocol], local_working_dir: _LocalWorkingDir -) -> _FileStrategy: - if acp.is_remote: - return _RemoteFileTransferStrategy( - local_working_directory=local_working_dir, - acp=acp, - ) - else: - return _LocalFileTransferStrategy( - local_working_directory=local_working_dir, - ) - - -# Todo: Add automated tests for local and remote workflow -class ACPWorkflow: - r"""Instantiate an ACP Workflow. - - Use the class methods :meth:`.from_cdb_or_dat_file` and - :meth:`.from_acph5_file` to instantiate the workflow. - - Parameters - ---------- - acp - The ACP Client. - local_file_path : - Path of the file to load. - file_format : - Format of the file to load. Options are ``"acp:h5"``, ``"ansys:cdb"``, - ``"ansys:dat"``, and ``"ansys:h5"``. - kwargs : - Additional keyword arguments to pass to the :meth:`.ACPInstance.import_model` method. - - """ - - def __init__( - self, - *, - acp: ACPInstance[ServerProtocol], - local_working_directory: PATH | None = None, - local_file_path: PATH, - file_format: str, - **kwargs: Any, - ): - self._acp_instance = acp - self._local_working_dir = _LocalWorkingDir(local_working_directory) - self._file_transfer_strategy = _get_file_transfer_strategy( - acp=self._acp_instance, - local_working_dir=self._local_working_dir, - ) - - uploaded_file = self._add_input_file(path=pathlib.Path(local_file_path)) - self._model = self._acp_instance.import_model( - path=uploaded_file, format=file_format, **kwargs - ) - - @classmethod - def from_acph5_file( - cls, - acp: ACPInstance[ServerProtocol], - acph5_file_path: PATH, - local_working_directory: PATH | None = None, - ) -> "ACPWorkflow": - """Instantiate an ACP Workflow from an acph5 file. - - Parameters - ---------- - acp - The ACP Client. - acph5_file_path: - The path to the acph5 file. - local_working_directory: - The local working directory. If None, a temporary directory will be created. - """ - - return cls( - acp=acp, - local_file_path=acph5_file_path, - local_working_directory=local_working_directory, - file_format="acp:h5", - ) - - @classmethod - def from_mechanical_h5_file( - cls, - acp: ACPInstance[ServerProtocol], - h5_file_path: PATH, - local_working_directory: PATH | None = None, - ) -> "ACPWorkflow": - """Instantiate an ACP Workflow from a Mechanical HDF5 file. - - Create an ACP Workflow from the Mechanical to ACP HDF5 transfer - file. This file can be created using the :func:`.export_mesh_for_acp` - function. - - Parameters - ---------- - acp: - The ACP Client. - h5_file_path: - The path to the HDF5 file. - local_working_directory: - The local working directory. If None, a temporary directory will be created. - """ - - return cls( - acp=acp, - local_file_path=h5_file_path, - local_working_directory=local_working_directory, - file_format="ansys:h5", - ) - - @classmethod - def from_cdb_or_dat_file( - cls, - *, - acp: ACPInstance[ServerProtocol], - cdb_or_dat_file_path: PATH, - unit_system: UnitSystemType = UnitSystemType.FROM_FILE, - local_working_directory: PATH | None = None, - **kwargs: Any, - ) -> "ACPWorkflow": - """Instantiate an ACP Workflow from a cdb file. - - Parameters - ---------- - acp - The ACP Client. - cdb_or_dat_file_path: - The path to the cdb or dat file. - unit_system: - Defines the unit system of the imported file. Must be set if the - input file does not have units. If the input file does have units, - ``unit_system`` must be either ``"from_file"``, or match the input - unit system. - local_working_directory: - The local working directory. If None, a temporary directory will be created. - ignored_entities: - Entities to ignore when loading the FE file. Can be a subset of - the following values: - ``"coordinate_systems"``, ``"element_sets"``, ``"materials"``, - ``"mesh"``, or ``"shell_sections"``. - Available only when the format is not ``"acp:h5"``. - convert_section_data: - Whether to import the section data of a shell model and convert it - into ACP composite definitions. - Available only when the format is not ``"acp:h5"``. - """ - - instance = cls( - acp=acp, - local_file_path=cdb_or_dat_file_path, - local_working_directory=local_working_directory, - file_format="ansys:cdb", - unit_system=unit_system, - **kwargs, - ) - - if instance.model.unit_system == UnitSystemType.UNDEFINED: - raise ValueError( - "The input file does not provide a unit system. Please specify the unit system." - ) - return instance - - @property - def model(self) -> Model: - """Get the ACP Model.""" - return self._model - - @property - def working_directory(self) -> _LocalWorkingDir: - """Get the working directory.""" - return self._local_working_dir - - def get_local_cdb_file(self) -> pathlib.Path: - """Get the cdb file on the local machine. - - Write the analysis model including the layup definition in cdb format, - copy it to the local working directory and return its path. - """ - return self._file_transfer_strategy.get_file( - self._model.export_analysis_model, self._model.name + ".cdb" - ) - - def get_local_materials_file(self) -> pathlib.Path: - """Get the materials.xml file on the local machine. - - Write the materials.xml file, copy it to the local working directory and return its path. - """ - return self._file_transfer_strategy.get_file(self._model.export_materials, "materials.xml") - - def get_local_composite_definitions_file(self) -> pathlib.Path: - """Get the composite definitions file on the local machine. - - Write the composite definitions file, copy it to the local working - directory and return its path. - """ - return self._file_transfer_strategy.get_file( - self._model.export_shell_composite_definitions, "ACPCompositeDefinitions.h5" - ) - - def get_local_acph5_file(self) -> pathlib.Path: - """Get the ACP Project file (in acph5 format) on the local machine. - - Save the acp model to an acph5 file, copy it to the local working - directory and return its path. - """ - return self._file_transfer_strategy.get_file(self._model.save, self._model.name + ".acph5") - - def add_cad_geometry_from_local_file(self, path: pathlib.Path) -> CADGeometry: - """Add a local CAD geometry to the ACP model. - - Parameters - ---------- - path: - The path to the CAD geometry file. - """ - uploaded_file = self._add_input_file(path=path) - return self._model.create_cad_geometry(external_path=str(uploaded_file)) - - def refresh_cad_geometry_from_local_file( - self, path: pathlib.Path, cad_geometry: CADGeometry - ) -> None: - """Refresh the CAD geometry from a local file. - - Parameters - ---------- - path: - The path to the CAD geometry file. - cad_geometry: - The CADGeometry object to refresh. - """ - uploaded_file_path = self._add_input_file(path=path) - cad_geometry.external_path = uploaded_file_path - cad_geometry.refresh() - - def _add_input_file(self, path: pathlib.Path) -> pathlib.PurePath: - self._file_transfer_strategy.copy_input_file_to_local_workdir(path=path) - return self._file_transfer_strategy.upload_input_file_to_server(path=path) - - -def get_composite_post_processing_files( - acp_workflow: ACPWorkflow, local_rst_file_path: PATH -) -> "ContinuousFiberCompositesFiles": - """Get the files object needed for pydpf-composites from the workflow and the rst path. - - Only supports the shell workflow. - - Parameters - ---------- - acp_workflow: - The ACPWorkflow object. - local_rst_file_path: - Local path to the rst file. - """ - - # Only import here to avoid dependency on ansys.dpf.composites if it is not used - try: - from ansys.dpf.composites.data_sources import ( - CompositeDefinitionFiles, - ContinuousFiberCompositesFiles, - ) - except ImportError as e: - raise ImportError( - "The composite post processing files can only be retrieved if the " - "ansys-dpf-composites package is installed." - ) from e - - composite_files = ContinuousFiberCompositesFiles( - rst=local_rst_file_path, - composite={ - "shell": CompositeDefinitionFiles( - definition=acp_workflow.get_local_composite_definitions_file() - ), - }, - engineering_data=acp_workflow.get_local_materials_file(), - ) - return composite_files - - -def get_dpf_unit_system(unit_system: UnitSystemType) -> "UnitSystem": - """Convert pyACP unit system to DPF unit system. - - Parameters - ---------- - unit_system - The pyACP unit system. - """ - try: - from ansys.dpf.core import unit_systems - except ImportError as e: - raise ImportError( - "The pyACP unit system can only be converted to a DPF unit system if the " - "ansys-dpf-core package is installed." - ) from e - - unit_systems_map = { - UnitSystemType.UNDEFINED: unit_systems.undefined, - # looks like the only difference from MKS to SI is - # that temperature is defined as Kelvin in SI and °C in MKS. - # We should still force the user to use MKS in this case. - UnitSystemType.SI: None, - UnitSystemType.MKS: unit_systems.solver_mks, - UnitSystemType.uMKS: unit_systems.solver_umks, - UnitSystemType.CGS: unit_systems.solver_cgs, - # MPA is equivalent to nmm - UnitSystemType.MPA: unit_systems.solver_nmm, - UnitSystemType.BFT: unit_systems.solver_bft, - UnitSystemType.BIN: unit_systems.solver_bin, - } - - if unit_systems_map[unit_system] is None: - raise ValueError(f"Unit system {unit_system} not supported. Use MKS instead of SI.") - if unit_system not in unit_systems_map: - raise ValueError(f"Unit system {unit_system} not supported.") - - return unit_systems_map[unit_system] diff --git a/src/ansys/acp/core/dpf_integration_helpers.py b/src/ansys/acp/core/dpf_integration_helpers.py new file mode 100644 index 0000000000..a7b37a0a98 --- /dev/null +++ b/src/ansys/acp/core/dpf_integration_helpers.py @@ -0,0 +1,72 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Helper functions for exchanging data between PyACP and PyDPF or PyDPF Composites.""" + +import typing + +from ._tree_objects.enums import UnitSystemType + +# Avoid dependencies on pydpf-composites and dpf-core if it is not used +if typing.TYPE_CHECKING: # pragma: no cover + from ansys.dpf.core import UnitSystem + +__all__ = ["get_dpf_unit_system"] + + +def get_dpf_unit_system(unit_system: UnitSystemType) -> "UnitSystem": + """Convert pyACP unit system to DPF unit system. + + Parameters + ---------- + unit_system + The pyACP unit system. + """ + try: + from ansys.dpf.core import unit_systems + except ImportError as e: + raise ImportError( + "The pyACP unit system can only be converted to a DPF unit system if the " + "ansys-dpf-core package is installed." + ) from e + + unit_systems_map = { + UnitSystemType.UNDEFINED: unit_systems.undefined, + # looks like the only difference from MKS to SI is + # that temperature is defined as Kelvin in SI and °C in MKS. + # We should still force the user to use MKS in this case. + UnitSystemType.SI: None, + UnitSystemType.MKS: unit_systems.solver_mks, + UnitSystemType.uMKS: unit_systems.solver_umks, + UnitSystemType.CGS: unit_systems.solver_cgs, + # MPA is equivalent to nmm + UnitSystemType.MPA: unit_systems.solver_nmm, + UnitSystemType.BFT: unit_systems.solver_bft, + UnitSystemType.BIN: unit_systems.solver_bin, + } + + if unit_systems_map[unit_system] is None: + raise ValueError(f"Unit system {unit_system} not supported. Use MKS instead of SI.") + if unit_system not in unit_systems_map: + raise ValueError(f"Unit system {unit_system} not supported.") + + return unit_systems_map[unit_system] diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index 3c121ebe46..aa67ae47ae 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -24,12 +24,12 @@ These utilities can download the input files used in the PyACP examples. """ - import dataclasses from enum import Enum, auto import pathlib import shutil import sys +import tempfile import urllib.parse import urllib.request @@ -38,7 +38,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: # pragma: no cover - from ansys.acp.core import ACPWorkflow + from ansys.acp.core import Model _EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" @@ -148,7 +148,7 @@ def _download_file( shutil.copyfile(file_url, local_path) -def _run_analysis(workflow: "ACPWorkflow") -> None: +def _run_analysis(model: "Model") -> None: """Run the model with mapdl and do a post-processing analysis. Uses a max strain criteria, which means strain limits have to be defined. @@ -157,50 +157,68 @@ def _run_analysis(workflow: "ACPWorkflow") -> None: """ from ansys.mapdl.core import launch_mapdl - model = workflow.model - model.update() - - # Launch the MAPDL instance - mapdl = launch_mapdl() - mapdl.clear() - - # Load the CDB file into PyMAPDL - mapdl.input(str(workflow.get_local_cdb_file())) - - # Solve the model - mapdl.allsel() - mapdl.slashsolu() - mapdl.solve() - - # Download the rst file for composite specific post-processing - rstfile_name = f"{mapdl.jobname}.rst" - rst_file_local_path = workflow.working_directory.path / rstfile_name - mapdl.download(rstfile_name, str(workflow.working_directory.path)) - - from ansys.acp.core import get_composite_post_processing_files, get_dpf_unit_system - from ansys.dpf.composites.composite_model import CompositeModel - from ansys.dpf.composites.constants import FailureOutput - from ansys.dpf.composites.failure_criteria import CombinedFailureCriterion, MaxStrainCriterion - from ansys.dpf.composites.server_helpers import connect_to_or_start_server - - dpf_server = connect_to_or_start_server() - - max_strain = MaxStrainCriterion() - - cfc = CombinedFailureCriterion( - name="Combined Failure Criterion", - failure_criteria=[max_strain], - ) - - composite_model = CompositeModel( - get_composite_post_processing_files(workflow, rst_file_local_path), - default_unit_system=get_dpf_unit_system(model.unit_system), - server=dpf_server, - ) - - output_all_elements = composite_model.evaluate_failure_criteria(cfc) - irf_field = output_all_elements.get_field({"failure_label": FailureOutput.FAILURE_VALUE}) - - # %% - # Release composite model to close open streams to result file. - composite_model = None # type: ignore + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_dir_path = pathlib.Path(tmp_dir) + model.update() + cdb_out_path = tmp_dir_path / "model.cdb" + model.export_analysis_model(cdb_out_path) + + # Launch the MAPDL instance + mapdl = launch_mapdl() + mapdl.clear() + + # Load the CDB file into PyMAPDL + mapdl.input(str(cdb_out_path)) + + # Solve the model + mapdl.allsel() + mapdl.slashsolu() + mapdl.solve() + + # Download the rst file for composite specific post-processing + rstfile_name = f"{mapdl.jobname}.rst" + rst_file_local_path = tmp_dir_path / rstfile_name + mapdl.download(rstfile_name, str(tmp_dir_path)) + + from ansys.acp.core.dpf_integration_helpers import get_dpf_unit_system + from ansys.dpf.composites.composite_model import CompositeModel + from ansys.dpf.composites.constants import FailureOutput + from ansys.dpf.composites.data_sources import ( + CompositeDefinitionFiles, + ContinuousFiberCompositesFiles, + ) + from ansys.dpf.composites.failure_criteria import ( + CombinedFailureCriterion, + MaxStrainCriterion, + ) + from ansys.dpf.composites.server_helpers import connect_to_or_start_server + + dpf_server = connect_to_or_start_server() + + max_strain = MaxStrainCriterion() + + cfc = CombinedFailureCriterion( + name="Combined Failure Criterion", + failure_criteria=[max_strain], + ) + + materials_file_path = tmp_dir_path / "materials.xml" + model.export_materials(materials_file_path) + composite_definitions_file_path = tmp_dir_path / "ACPCompositeDefinitions.h5" + model.export_shell_composite_definitions(composite_definitions_file_path) + composite_model = CompositeModel( + composite_files=ContinuousFiberCompositesFiles( + rst=rst_file_local_path, + composite={"shell": CompositeDefinitionFiles(composite_definitions_file_path)}, + engineering_data=materials_file_path, + ), + default_unit_system=get_dpf_unit_system(model.unit_system), + server=dpf_server, + ) + + output_all_elements = composite_model.evaluate_failure_criteria(cfc) + irf_field = output_all_elements.get_field({"failure_label": FailureOutput.FAILURE_VALUE}) + + # %% + # Release composite model to close open streams to result file. + composite_model = None # type: ignore diff --git a/tests/benchmarks/test_class40.py b/tests/benchmarks/test_class40.py index 90e4b36b77..78e48ded3c 100644 --- a/tests/benchmarks/test_class40.py +++ b/tests/benchmarks/test_class40.py @@ -36,10 +36,8 @@ def create_class40(pyacp_client, cdb_file): """ Create a composite lay-up for the Class40 model. """ - cdb_file_path = pyacp_client.upload_file(local_path=cdb_file) - model = pyacp_client.import_model( - path=cdb_file_path, format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA + path=cdb_file, format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA ) # Materials diff --git a/tests/conftest.py b/tests/conftest.py index b2908df7a3..537f76b02d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -239,13 +239,9 @@ def load_model_from_tempfile(model_data_dir, acp_instance): def inner(relative_file_path="minimal_complete_model_no_matml_link.acph5", format="acp:h5"): with tempfile.TemporaryDirectory() as tmp_dir: source_path = model_data_dir / relative_file_path - - if acp_instance.is_remote: - file_path = acp_instance.upload_file(source_path) - else: - # Copy the file to a temporary directory, so the original file is never - # modified. This can happen for example when a geometry reload happens. - file_path = shutil.copy(source_path, tmp_dir) + # Copy the file to a temporary directory, so the original file is never + # modified. This can happen for example when a geometry reload happens. + file_path = shutil.copy(source_path, tmp_dir) yield acp_instance.import_model(path=file_path, format=format) @@ -259,12 +255,9 @@ def inner(relative_file_path="minimal_model_imported_plies.acph5", format="acp:h with tempfile.TemporaryDirectory() as tmp_dir: source_path = model_data_dir / relative_file_path - if acp_instance.is_remote: - file_path = acp_instance.upload_file(source_path) - else: - # Copy the file to a temporary directory, so the original file is never - # modified. This can happen for example when a geometry reload happens. - file_path = shutil.copy(source_path, tmp_dir) + # Copy the file to a temporary directory, so the original file is never + # modified. This can happen for example when a geometry reload happens. + file_path = shutil.copy(source_path, tmp_dir) yield acp_instance.import_model(path=file_path, format=format) @@ -275,9 +268,10 @@ def inner(relative_file_path="minimal_model_imported_plies.acph5", format="acp:h def load_cad_geometry(model_data_dir, acp_instance): @contextmanager def inner(model, relative_file_path="square_and_solid.stp"): - cad_file_path = acp_instance.upload_file(model_data_dir / relative_file_path) + cad_file_path_local = model_data_dir / relative_file_path + cad_file_path_remote = acp_instance.upload_file(cad_file_path_local) yield model.create_cad_geometry( - external_path=cad_file_path, + external_path=cad_file_path_remote, ) return inner @@ -307,21 +301,3 @@ def inner(version: str): pytest.skip(f"Test is not supported before version {version}") return inner - - -@pytest.fixture -def tempdir_if_local_acp(acp_instance): - """ - Context manager which provides a temporary directory if the ACP server is local. - Otherwise, an empty path is provided. - """ - - @contextmanager - def inner(): - if acp_instance.is_remote: - yield pathlib.PurePosixPath(".") - else: - with tempfile.TemporaryDirectory() as tmp_dir: - yield pathlib.Path(tmp_dir) - - return inner diff --git a/tests/unittests/test_acp_instance.py b/tests/unittests/test_acp_instance.py index f11c4d6855..22a7c7ade2 100644 --- a/tests/unittests/test_acp_instance.py +++ b/tests/unittests/test_acp_instance.py @@ -21,6 +21,15 @@ # SOFTWARE. +from contextlib import contextmanager +import os +import pathlib +import shutil +import tempfile + +import pytest + + def test_server_version(acp_instance): version = acp_instance.server_version assert isinstance(version, str) @@ -40,3 +49,29 @@ def test_models(acp_instance, load_model_from_tempfile): acp_instance.clear() assert acp_instance.models == () + + +@contextmanager +def change_cwd(new_cwd): + old_cwd = os.getcwd() + os.chdir(new_cwd) + try: + yield + finally: + os.chdir(old_cwd) + + +def test_import_from_differenct_cwd(acp_instance, model_data_dir): + if acp_instance.is_remote: + pytest.skip("Test is not relevant for remote instances") + with tempfile.TemporaryDirectory() as tmp_dir: + model_filename = "minimal_complete_model.acph5" + tmp_dir_path = pathlib.Path(tmp_dir) + shutil.copyfile(model_data_dir / model_filename, tmp_dir_path / model_filename) + with change_cwd(tmp_dir): + model = acp_instance.import_model(model_filename) + export_filename = "exported_model.acph5" + model.save(export_filename) + assert (tmp_dir_path / export_filename).exists() + # Check that the exported file does not exist on the original CWD + assert not pathlib.Path(export_filename).exists() diff --git a/tests/unittests/test_cad_geometry.py b/tests/unittests/test_cad_geometry.py index 49ada9796c..fcfbf50764 100644 --- a/tests/unittests/test_cad_geometry.py +++ b/tests/unittests/test_cad_geometry.py @@ -76,19 +76,22 @@ def object_properties(parent_object): ) @staticmethod - def test_refresh(load_cad_geometry, load_model_from_tempfile): + def test_refresh(load_model_from_tempfile, model_data_dir): """Test refreshing the geometry. Only tests that the call does not raise an exception. """ - with load_model_from_tempfile() as model, load_cad_geometry(model=model) as cad_geometry: - cad_geometry.refresh() + with load_model_from_tempfile() as model: + cad_geometry = model.create_cad_geometry() + local_cad_path = model_data_dir / "square_and_solid.stp" + assert local_cad_path.exists() + cad_geometry.refresh(local_cad_path) @staticmethod def test_refresh_inexistent_raises(tree_object): """Test refreshing the geometry from an inexistent file.""" - with pytest.raises(RuntimeError, match="source file `[^`]*` does not exist"): - tree_object.refresh() + with pytest.raises(FileNotFoundError): + tree_object.refresh("inexistent_file") @staticmethod def test_accessing_root_shapes(load_cad_geometry, load_model_from_tempfile): diff --git a/tests/unittests/test_edge_property_list.py b/tests/unittests/test_edge_property_list.py index 4ff8d446c2..3ca4919516 100644 --- a/tests/unittests/test_edge_property_list.py +++ b/tests/unittests/test_edge_property_list.py @@ -79,10 +79,7 @@ def test_save_load_with_existing_entries( # WHEN: the model is saved and loaded with tempfile.TemporaryDirectory() as tmp_dir: - if not acp_instance.is_remote: - file_path: PATH = pathlib.Path(tmp_dir) / "model.acph5" - else: - file_path = "model.acph5" + file_path: PATH = pathlib.Path(tmp_dir) / "model.acph5" model.save(file_path) acp_instance.clear() model = acp_instance.import_model(path=file_path) diff --git a/tests/unittests/test_imported_solid_model.py b/tests/unittests/test_imported_solid_model.py index ac0e00a071..1a9081c457 100644 --- a/tests/unittests/test_imported_solid_model.py +++ b/tests/unittests/test_imported_solid_model.py @@ -125,17 +125,18 @@ def object_properties(parent_object): @pytest.fixture -def imported_solid_model_with_elements(parent_object, tempdir_if_local_acp): +def imported_solid_model_with_elements(acp_instance, parent_object): model = parent_object solid_model = model.create_solid_model() solid_model.element_sets = [model.element_sets["All_Elements"]] model.update() - with tempdir_if_local_acp() as export_dir: - filename = export_dir / "solid_model.h5" + with tempfile.TemporaryDirectory() as tmp_dir: + filename = pathlib.Path(tmp_dir) / "solid_model.h5" solid_model.export(filename, format=pyacp.SolidModelExportFormat.ANSYS_H5) + assert filename.exists() del model.solid_models[solid_model.id] imported_solid_model = model.create_imported_solid_model( - external_path=filename, + external_path=acp_instance.upload_file(filename), format=pyacp.SolidModelImportFormat.ANSYS_H5, ) imported_solid_model.create_layup_mapping_object( @@ -154,95 +155,80 @@ def imported_solid_model_with_elements(parent_object, tempdir_if_local_acp): pyacp.SolidModelExportFormat.ANSYS_CDB, ], ) -def test_export(tempdir_if_local_acp, acp_instance, imported_solid_model_with_elements, format): +def test_export(imported_solid_model_with_elements, format): """Check that the export to a file works.""" - with tempdir_if_local_acp() as export_dir: - with tempfile.TemporaryDirectory() as tmp_dir: - if format == "ansys:h5": - ext = ".h5" - else: - ext = ".cdb" + with tempfile.TemporaryDirectory() as tmp_dir: + if format == "ansys:h5": + ext = ".h5" + else: + ext = ".cdb" - out_file_name = f"out_file{ext}" - out_path = export_dir / out_file_name - tmp_path = pathlib.Path(tmp_dir) / out_file_name + out_path = pathlib.Path(tmp_dir) / f"out_file{ext}" + imported_solid_model_with_elements.export(path=out_path, format=format) - imported_solid_model_with_elements.export(path=out_file_name, format=format) - acp_instance.download_file(out_path, tmp_path) - - assert tmp_path.exists() - assert tmp_path.stat().st_size > 0 + assert out_path.exists() + assert out_path.stat().st_size > 0 @given(invalid_format=st.text()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) -def test_export_with_invalid_format_raises( - imported_solid_model_with_elements, tempdir_if_local_acp, invalid_format -): +def test_export_with_invalid_format_raises(imported_solid_model_with_elements, invalid_format): """Check that the export to a file with an invalid format raises an exception.""" assume(invalid_format not in ["ansys:h5", "ansys:cdb"]) - with tempdir_if_local_acp() as export_dir: - out_file_name = f"out_file.h5" - out_path = export_dir / out_file_name + with tempfile.TemporaryDirectory() as tmp_dir: + out_path = pathlib.Path(tmp_dir) / f"out_file.h5" with pytest.raises(ValueError): imported_solid_model_with_elements.export(path=out_path, format=invalid_format) @pytest.mark.parametrize("format", ["ansys:cdb", "step", "iges", "stl"]) -def test_skin_export( - tempdir_if_local_acp, acp_instance, imported_solid_model_with_elements, format -): +def test_skin_export(imported_solid_model_with_elements, format): """Check that the skin export to a file works.""" - with tempdir_if_local_acp() as export_dir: - with tempfile.TemporaryDirectory() as tmp_dir: - ext = {"ansys:cdb": ".cdb", "step": ".stp", "iges": ".igs", "stl": ".stl"}[format] + with tempfile.TemporaryDirectory() as tmp_dir: + ext = {"ansys:cdb": ".cdb", "step": ".stp", "iges": ".igs", "stl": ".stl"}[format] - out_file_name = f"out_file{ext}" - out_path = export_dir / out_file_name - tmp_path = pathlib.Path(tmp_dir) / out_file_name + out_path = pathlib.Path(tmp_dir) / f"out_file{ext}" - imported_solid_model_with_elements.export_skin(path=out_file_name, format=format) - acp_instance.download_file(out_path, tmp_path) + imported_solid_model_with_elements.export_skin(path=out_path, format=format) - assert tmp_path.exists() - assert tmp_path.stat().st_size > 0 + assert out_path.exists() + assert out_path.stat().st_size > 0 @given(invalid_format=st.text()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) -def test_skin_export_with_invalid_format_raises( - imported_solid_model_with_elements, tempdir_if_local_acp, invalid_format -): +def test_skin_export_with_invalid_format_raises(imported_solid_model_with_elements, invalid_format): """Check that the export to a file with an invalid format raises an exception.""" assume(invalid_format not in ["ansys:cdb", "step", "iges", "stl"]) - with tempdir_if_local_acp() as export_dir: - out_file_name = f"out_file.h5" - out_path = export_dir / out_file_name + with tempfile.TemporaryDirectory() as tmp_dir: + out_path = pathlib.Path(tmp_dir) / f"out_file.h5" with pytest.raises(ValueError): imported_solid_model_with_elements.export_skin(path=out_path, format=invalid_format) -def test_refresh(tempdir_if_local_acp, parent_object): +def test_refresh(parent_object, acp_instance): """Check that refreshing works. Does not check the result of the refresh.""" model = parent_object solid_model = model.create_solid_model() solid_model.element_sets = [model.element_sets["All_Elements"]] model.update() - with tempdir_if_local_acp() as tmp_dir: + with tempfile.TemporaryDirectory() as tmp_dir: out_file_name = f"out_file.h5" out_path = pathlib.Path(tmp_dir) / out_file_name solid_model.export(path=out_path, format=pyacp.SolidModelExportFormat.ANSYS_H5) imported_solid_model = model.create_imported_solid_model( - external_path=out_path, + external_path=acp_instance.upload_file(out_path), format=pyacp.SolidModelImportFormat.ANSYS_H5, ) - imported_solid_model.refresh() + model.update() + imported_solid_model.refresh(out_path) + model.update() @given(external_path=st.text()) @@ -252,25 +238,23 @@ def test_refresh_inexistent_path(parent_object, external_path): assume(not pathlib.Path(external_path).exists()) model = parent_object imported_solid_model = model.create_imported_solid_model() - imported_solid_model.external_path = external_path - with pytest.raises(RuntimeError): - imported_solid_model.refresh() + with pytest.raises((OSError, ValueError)): + imported_solid_model.refresh(external_path) -def test_import_initial_mesh(tempdir_if_local_acp, parent_object): +def test_import_initial_mesh(acp_instance, parent_object): """Check that the 'import_initial_mesh' method works. Does not check the result.""" model = parent_object solid_model = model.create_solid_model() solid_model.element_sets = [model.element_sets["All_Elements"]] model.update() - with tempdir_if_local_acp() as tmp_dir: - out_file_name = f"out_file.h5" - out_path = pathlib.Path(tmp_dir) / out_file_name + with tempfile.TemporaryDirectory() as tmp_dir: + out_path = pathlib.Path(tmp_dir) / f"out_file.h5" solid_model.export(path=out_path, format=pyacp.SolidModelExportFormat.ANSYS_H5) imported_solid_model = model.create_imported_solid_model( - external_path=out_path, + external_path=acp_instance.upload_file(out_path), format=pyacp.SolidModelImportFormat.ANSYS_H5, ) imported_solid_model.import_initial_mesh() diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index df9740b693..827cd781da 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -41,9 +41,7 @@ def test_unittest(acp_instance, model_data_dir): Test basic properties of the model object """ input_file_path = model_data_dir / "ACP-Pre.h5" - remote_path = acp_instance.upload_file(input_file_path) - - model = acp_instance.import_model(name="kiteboard", path=remote_path, format="ansys:h5") + model = acp_instance.import_model(name="kiteboard", path=input_file_path, format="ansys:h5") # TODO: re-activate these tests when the respective features are implemented # assert model.unit_system.type == "mks" @@ -78,19 +76,11 @@ def test_unittest(acp_instance, model_data_dir): os.makedirs(working_dir) # model.solver.working_dir = str(working_dir) - if acp_instance.is_remote: - save_path: str | pathlib.Path = os.path.join( - os.path.dirname(remote_path), "test_model_serialization.acph5" - ) + with tempfile.TemporaryDirectory() as local_working_dir: + save_path = pathlib.Path(local_working_dir) / "test_model_serialization.acph5" model.save(save_path, save_cache=True) acp_instance.clear() model = acp_instance.import_model(path=save_path) - else: - with tempfile.TemporaryDirectory() as local_working_dir: - save_path = pathlib.Path(local_working_dir) / "test_model_serialization.acph5" - model.save(save_path, save_cache=True) - acp_instance.clear() - model = acp_instance.import_model(path=save_path) # TODO: re-activate these tests when the respective features are implemented # assert model.unit_system.type == "mks" @@ -128,32 +118,21 @@ def test_export_analysis_model(acp_instance, model_data_dir): are not checked. """ input_file_path = model_data_dir / "minimal_model_2.cdb" - remote_file_path = acp_instance.upload_file(input_file_path) - remote_workdir = remote_file_path.parent model = acp_instance.import_model( - name="minimal_model", path=remote_file_path, format="ansys:cdb", unit_system="mpa" + name="minimal_model", path=input_file_path, format="ansys:cdb", unit_system="mpa" ) - if acp_instance.is_remote: - out_file_path = remote_workdir / "out_file.cdb" - model.export_analysis_model(out_file_path) - with tempfile.TemporaryDirectory() as tmp_dir: - local_file_path = pathlib.Path(tmp_dir, "out_file.cdb") - acp_instance.download_file(out_file_path, local_file_path) - assert local_file_path.exists() - else: - with tempfile.TemporaryDirectory() as tmp_dir: - local_file_path = pathlib.Path(tmp_dir) / "out_file.cdb" - model.export_analysis_model(local_file_path) - assert local_file_path.exists() + with tempfile.TemporaryDirectory() as tmp_dir: + local_file_path = pathlib.Path(tmp_dir) / "out_file.cdb" + model.export_analysis_model(local_file_path) + assert local_file_path.exists() def test_string_representation(acp_instance, model_data_dir): input_file_path = model_data_dir / "ACP-Pre.h5" - remote_file_path = acp_instance.upload_file(input_file_path) model = acp_instance.import_model( name="minimal_model", - path=remote_file_path, + path=input_file_path, format="ansys:cdb", unit_system=UnitSystemType.MKS, ) @@ -271,16 +250,11 @@ def test_modeling_ply_export(acp_instance, minimal_complete_model, raises_before out_filename = "modeling_ply_export.step" with tempfile.TemporaryDirectory() as tmp_dir: - local_file_path = pathlib.Path(tmp_dir) / out_filename - if acp_instance.is_remote: - out_file_path = pathlib.Path(out_filename) - else: - out_file_path = local_file_path + out_file_path = pathlib.Path(tmp_dir) / out_filename with raises_before_version("25.1"): minimal_complete_model.export_modeling_ply_geometries(out_file_path) - acp_instance.download_file(out_file_path, local_file_path) - assert local_file_path.exists() + assert out_file_path.exists() def test_parent_access_raises(minimal_complete_model): @@ -325,25 +299,21 @@ def test_change_unit_system(minimal_complete_model, unit_system, raises_before_v ) -def test_material_export(acp_instance, minimal_complete_model, tempdir_if_local_acp): +def test_material_export(minimal_complete_model): """Check that the 'export_materials' method produces a file.""" - with tempdir_if_local_acp() as export_dir: - with tempfile.TemporaryDirectory() as tmp_dir: - export_file_path = pathlib.Path(export_dir) / "material_exported.xml" - local_file_path = pathlib.Path(tmp_dir) / "material_exported.xml" - - minimal_complete_model.export_materials(export_file_path) - acp_instance.download_file(export_file_path, local_file_path) + with tempfile.TemporaryDirectory() as tmp_dir: + export_path = pathlib.Path(tmp_dir) / "material_exported.xml" + minimal_complete_model.export_materials(export_path) - assert local_file_path.exists() - assert os.stat(local_file_path).st_size > 0 + assert export_path.exists() + assert os.stat(export_path).st_size > 0 -def test_material_import(minimal_complete_model, tempdir_if_local_acp, raises_before_version): +def test_material_import(minimal_complete_model, raises_before_version): # GIVEN: a model, and a material XML file containing a material which is # not present in the model - with tempdir_if_local_acp() as export_dir: + with tempfile.TemporaryDirectory() as export_dir: new_mat_id = "New Material" export_file_path = pathlib.Path(export_dir) / "material_exported.xml" mat = minimal_complete_model.materials["Structural Steel"].clone() @@ -373,47 +343,40 @@ def test_hdf5_composite_cae_export( acp_instance, minimal_complete_model, raises_before_version, - tempdir_if_local_acp, layup_representation_3d, offset_type, ): with raises_before_version("25.1"): - with tempdir_if_local_acp() as export_dir: - with tempfile.TemporaryDirectory() as tmp_dir: - export_file = export_dir / "model_export.h5" - local_file = pathlib.Path(tmp_dir) / "model_export.h5" - minimal_complete_model.export_hdf5_composite_cae( - export_file, - layup_representation_3d=layup_representation_3d, - offset_type=offset_type, - ) - acp_instance.download_file(export_file, local_file) + with tempfile.TemporaryDirectory() as tmp_dir: + export_file = pathlib.Path(tmp_dir) / "model_export.h5" + minimal_complete_model.export_hdf5_composite_cae( + export_file, + layup_representation_3d=layup_representation_3d, + offset_type=offset_type, + ) - assert local_file.exists() - assert os.stat(local_file).st_size > 0 + assert export_file.exists() + assert os.stat(export_file).st_size > 0 def test_hdf5_composite_cae_export_with_scope( - acp_instance, minimal_complete_model, raises_before_version, tempdir_if_local_acp + acp_instance, minimal_complete_model, raises_before_version ): with raises_before_version("25.1"): - with tempdir_if_local_acp() as export_dir: - with tempfile.TemporaryDirectory() as tmp_dir: - export_file = export_dir / "model_export.h5" - local_file = pathlib.Path(tmp_dir) / "model_export.h5" - minimal_complete_model.export_hdf5_composite_cae( - export_file, - all_elements=False, - element_sets=minimal_complete_model.element_sets.values(), - all_plies=False, - plies=minimal_complete_model.modeling_groups[ - "ModelingGroup.1" - ].modeling_plies.values(), - ) - acp_instance.download_file(export_file, local_file) - - assert local_file.exists() - assert os.stat(local_file).st_size > 0 + with tempfile.TemporaryDirectory() as tmp_dir: + export_file = pathlib.Path(tmp_dir) / "model_export.h5" + minimal_complete_model.export_hdf5_composite_cae( + export_file, + all_elements=False, + element_sets=minimal_complete_model.element_sets.values(), + all_plies=False, + plies=minimal_complete_model.modeling_groups[ + "ModelingGroup.1" + ].modeling_plies.values(), + ) + + assert export_file.exists() + assert os.stat(export_file).st_size > 0 @pytest.mark.parametrize( @@ -427,13 +390,12 @@ def test_hdf5_composite_cae_export_with_scope( def test_hdf5_composite_cae_export_import( minimal_complete_model, raises_before_version, - tempdir_if_local_acp, import_mode, projection_mode, ): with raises_before_version("25.1"): - with tempdir_if_local_acp() as export_dir: - export_file = export_dir / "model_export.h5" + with tempfile.TemporaryDirectory() as tmp_dir: + export_file = pathlib.Path(tmp_dir) / "model_export.h5" minimal_complete_model.export_hdf5_composite_cae(export_file) minimal_complete_model.import_hdf5_composite_cae( export_file, diff --git a/tests/unittests/test_solid_model.py b/tests/unittests/test_solid_model.py index b07060988e..3347870d4f 100644 --- a/tests/unittests/test_solid_model.py +++ b/tests/unittests/test_solid_model.py @@ -298,15 +298,8 @@ def test_solid_model_export(acp_instance, parent_object, format): else: ext = ".cdb" - out_file_name = f"out_file{ext}" - out_path = pathlib.Path(tmp_dir) / out_file_name - - if not acp_instance.is_remote: - # save directly to the local file, to avoid a copy in the working directory - out_file_name = out_path # type: ignore - - solid_model.export(path=out_file_name, format=format) - acp_instance.download_file(out_file_name, out_path) + out_path = pathlib.Path(tmp_dir) / f"out_file{ext}" + solid_model.export(path=out_path, format=format) assert out_path.exists() assert out_path.stat().st_size > 0 @@ -341,15 +334,9 @@ def test_solid_model_skin_export(acp_instance, parent_object, format): with tempfile.TemporaryDirectory() as tmp_dir: ext = {"ansys:cdb": ".cdb", "step": ".stp", "iges": ".igs", "stl": ".stl"}[format] - out_file_name = f"out_file{ext}" - out_path = pathlib.Path(tmp_dir) / out_file_name - - if not acp_instance.is_remote: - # save directly to the local file, to avoid a copy in the working directory - out_file_name = out_path # type: ignore + out_path = pathlib.Path(tmp_dir) / f"out_file{ext}" - solid_model.export_skin(path=out_file_name, format=format) - acp_instance.download_file(out_file_name, out_path) + solid_model.export_skin(path=out_path, format=format) assert out_path.exists() assert out_path.stat().st_size > 0 diff --git a/tests/unittests/test_tree_printer.py b/tests/unittests/test_tree_printer.py index d80a00b401..198762c87a 100644 --- a/tests/unittests/test_tree_printer.py +++ b/tests/unittests/test_tree_printer.py @@ -30,9 +30,7 @@ def test_printed_model(acp_instance, model_data_dir): Test that model tree looks correct. """ input_file_path = model_data_dir / "minimal_complete_model_no_matml_link.acph5" - remote_path = acp_instance.upload_file(input_file_path) - - model = acp_instance.import_model(name="minimal_complete", path=remote_path) + model = acp_instance.import_model(name="minimal_complete", path=input_file_path) model.update() tree = get_model_tree(model) diff --git a/tests/unittests/test_workflow.py b/tests/unittests/test_workflow.py deleted file mode 100644 index b0c53e04b0..0000000000 --- a/tests/unittests/test_workflow.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import pathlib -import shutil -import tempfile - -from packaging.version import parse as parse_version -import pytest - -from ansys.acp.core import ACPWorkflow, UnitSystemType - - -@pytest.mark.parametrize("explict_temp_dir", [None, tempfile.TemporaryDirectory()]) -def test_workflow(acp_instance, model_data_dir, explict_temp_dir): - """Test that workflow can be initialized and files can be retrieved.""" - input_file_path = model_data_dir / "minimal_model_2.cdb" - - if explict_temp_dir is not None: - working_dir = pathlib.Path(explict_temp_dir.name) - else: - working_dir = None - - workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp_instance, - cdb_or_dat_file_path=input_file_path, - local_working_directory=working_dir, - unit_system=UnitSystemType.MPA, - ) - workflow.model.update() - - cbd_path = workflow.get_local_cdb_file() - assert cbd_path == workflow.working_directory.path / f"{workflow.model.name}.cdb" - assert cbd_path.is_file() - - acph5_path = workflow.get_local_acph5_file() - assert acph5_path == workflow.working_directory.path / f"{workflow.model.name}.acph5" - assert acph5_path.is_file() - - materials_path = workflow.get_local_materials_file() - assert materials_path == workflow.working_directory.path / "materials.xml" - assert materials_path.is_file() - - composite_definitions = workflow.get_local_composite_definitions_file() - assert composite_definitions == workflow.working_directory.path / "ACPCompositeDefinitions.h5" - assert composite_definitions.is_file() - - -@pytest.mark.parametrize("explict_temp_dir", [None, tempfile.TemporaryDirectory()]) -def test_workflow_from_mechanical_h5(acp_instance, model_data_dir, explict_temp_dir): - """Test that workflow can be initialized from a Mechanical HDF5 transfer file.""" - input_file_path = model_data_dir / "ACP-Pre.h5" - - if explict_temp_dir is not None: - working_dir = pathlib.Path(explict_temp_dir.name) - else: - working_dir = None - - workflow = ACPWorkflow.from_mechanical_h5_file( - acp=acp_instance, - h5_file_path=input_file_path, - local_working_directory=working_dir, - ) - workflow.model.update() - - with pytest.raises(RuntimeError): - workflow.get_local_cdb_file() - - acph5_path = workflow.get_local_acph5_file() - assert acph5_path == workflow.working_directory.path / f"{workflow.model.name}.acph5" - assert acph5_path.is_file() - - materials_path = workflow.get_local_materials_file() - assert materials_path == workflow.working_directory.path / "materials.xml" - assert materials_path.is_file() - - composite_definitions = workflow.get_local_composite_definitions_file() - assert composite_definitions == workflow.working_directory.path / "ACPCompositeDefinitions.h5" - assert composite_definitions.is_file() - - -def test_reload_cad_geometry(acp_instance, model_data_dir, load_cad_geometry): - input_file_path = model_data_dir / "minimal_model_2.cdb" - - workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp_instance, - cdb_or_dat_file_path=input_file_path, - unit_system=UnitSystemType.MPA, - ) - workflow.model.update() - - cad_geometry_file_path = model_data_dir / "square_and_solid.stp" - cad_geometry = workflow.add_cad_geometry_from_local_file(cad_geometry_file_path) - - with tempfile.TemporaryDirectory() as tempdir: - copied_path = pathlib.Path(shutil.copy(cad_geometry_file_path, tempdir)) - - workflow.refresh_cad_geometry_from_local_file(copied_path, cad_geometry) - if acp_instance.is_remote: - assert cad_geometry.external_path == str(copied_path.name) - else: - assert cad_geometry.external_path == str(copied_path) - - # Test that refresh works twice with the same local file. - workflow.refresh_cad_geometry_from_local_file(copied_path, cad_geometry) - - # Test error when local file does not exist. - pathlib.Path.unlink(copied_path) - with pytest.raises(FileNotFoundError) as excinfo: - workflow.refresh_cad_geometry_from_local_file(copied_path, cad_geometry) - assert "No such file or directory" in str(excinfo.value) - - -@pytest.mark.parametrize("unit_system", UnitSystemType) -def test_workflow_unit_system_dat(acp_instance, model_data_dir, unit_system): - """Test that workflow can be initialized and files can be retrieved.""" - - input_file_path = model_data_dir / "flat_plate_input.dat" - - # Initializing a workflow with a defined unit system is not allowed if the - # input file does contain the unit system. - # Since 25.1, we also allow it if the unit systems match. - if parse_version(acp_instance.server_version) < parse_version("25.1"): - allowed_unit_system_values = [UnitSystemType.UNDEFINED, UnitSystemType.FROM_FILE] - else: - allowed_unit_system_values = [ - UnitSystemType.UNDEFINED, - UnitSystemType.FROM_FILE, - UnitSystemType.MKS, - ] - - if unit_system not in allowed_unit_system_values: - with pytest.raises(ValueError) as ex: - ACPWorkflow.from_cdb_or_dat_file( - acp=acp_instance, - cdb_or_dat_file_path=input_file_path, - unit_system=unit_system, - ) - else: - workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp_instance, - cdb_or_dat_file_path=input_file_path, - unit_system=unit_system, - ) - # Unit system in the dat file is MKS - assert workflow.model.unit_system == UnitSystemType.MKS - - -@pytest.mark.parametrize("unit_system", UnitSystemType) -def test_workflow_unit_system_cdb(acp_instance, model_data_dir, unit_system): - """Test that workflow can be initialized and files can be retrieved.""" - - input_file_path = model_data_dir / "minimal_model_2.cdb" - - if unit_system in (UnitSystemType.UNDEFINED, UnitSystemType.FROM_FILE): - with pytest.raises(ValueError) as ex: - # Initializing a workflow with an undefined unit system is not allowed - # if the input file does not contain the unit system. - ACPWorkflow.from_cdb_or_dat_file( - acp=acp_instance, - cdb_or_dat_file_path=input_file_path, - unit_system=unit_system, - ) - else: - workflow = ACPWorkflow.from_cdb_or_dat_file( - acp=acp_instance, - cdb_or_dat_file_path=input_file_path, - unit_system=unit_system, - ) - assert workflow.model.unit_system == unit_system - - -def test_workflow_ignored_entities(acp_instance, model_data_dir): - """Test that workflow the keyword argument for ignored entities.""" - input_file_path = model_data_dir / "minimal_model_2.cdb" - - kwargs = { - "acp": acp_instance, - "cdb_or_dat_file_path": input_file_path, - "unit_system": UnitSystemType.MPA, - } - - workflow_without_ignored_entities = ACPWorkflow.from_cdb_or_dat_file(**kwargs) - assert len(workflow_without_ignored_entities.model.rosettes) > 0 - - workflow = ACPWorkflow.from_cdb_or_dat_file(ignored_entities=["coordinate_systems"], **kwargs) - assert len(workflow.model.rosettes) == 0 From 218e03fd5e12c3407a38aa52df2bb8085f5dd6a3 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 26 Nov 2024 14:14:57 +0100 Subject: [PATCH 76/96] Add CI tests with 'direct' launch mode (#700) Add a Dockerfile which can be used to run the tests with the 'direct' launch mode: - the ACP image is used as a base image - the PyACP code is bind-mounted into the container - the container executes 'poetry install' and 'poetry run pytest' with the appropriate arguments Note that the Python version is determined by the ACP base image. This means it is not completely trivial to test with different Python versions. --- .github/workflows/ci_cd.yml | 35 +++++++++++++++- dockerfiles/DirectLaunchTest.Dockerfile | 41 +++++++++++++++++++ .../DirectLaunchTest.Dockerfile.dockerignore | 6 +++ tests/unittests/test_cad_geometry.py | 2 +- tests/unittests/test_imported_solid_model.py | 2 +- 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 dockerfiles/DirectLaunchTest.Dockerfile create mode 100644 dockerfiles/DirectLaunchTest.Dockerfile.dockerignore diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 459fff9070..a196d1b860 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -118,6 +118,39 @@ jobs: dev-mode: ${{ github.ref != 'refs/heads/main' }} extra-targets: "plotting" + testing-direct-launch: + name: Testing with direct launch mode + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v4 + - name: Login in Github Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # Python version is determined by the base image + - name: Build docker image + run: docker build -t test_runner -f dockerfiles/DirectLaunchTest.Dockerfile . + - name: Run tests + run: | + docker run \ + --mount type=bind,source="$(pwd)",target=/home/container/pyacp \ + -u $(id -u):$(id -g) \ + -e ANSYSLMD_LICENSE_FILE=$ANSYSLMD_LICENSE_FILE \ + test_runner + env: + ANSYSLMD_LICENSE_FILE: "1055@${{ secrets.LICENSE_SERVER }}" + - name: "Upload coverage to Codecov" + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: coverage.xml + flags: 'direct-launch,server-latest' + testing-minimum-deps: name: Testing with minimum dependencies runs-on: ubuntu-latest @@ -513,7 +546,7 @@ jobs: build: name: Build library runs-on: ubuntu-latest - needs: [code-style, testing, testing-minimum-deps, doc-style, docs, build-wheelhouse, doctest] # TODO: add check-vulnerabilities once we know it works on main + needs: [code-style, testing, testing-minimum-deps, testing-direct-launch, doc-style, docs, build-wheelhouse, doctest] # TODO: add check-vulnerabilities once we know it works on main timeout-minutes: 30 steps: - name: Build library source and wheel artifacts diff --git a/dockerfiles/DirectLaunchTest.Dockerfile b/dockerfiles/DirectLaunchTest.Dockerfile new file mode 100644 index 0000000000..fd24ca3093 --- /dev/null +++ b/dockerfiles/DirectLaunchTest.Dockerfile @@ -0,0 +1,41 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE=ghcr.io/ansys/acp:latest + +FROM $BASE_IMAGE + +USER root + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y \ + python3 \ + python3-pip \ + python3-venv \ + pipx \ + libxrender1 \ + && \ + rm -rf /var/lib/apt/lists/* + + +USER container + +RUN pipx install poetry + +ENV PATH="$PATH:/home/container/.local/bin" + +# COPY --chown=container:container . /home/container/pyacp + +WORKDIR /home/container/pyacp + +# Make /home/container writable to any user +RUN chmod -R 777 /home/container/ + +COPY --chmod=755 < Date: Tue, 26 Nov 2024 15:10:57 +0100 Subject: [PATCH 77/96] Remove deprecated version in Docker compose files (#704) The `version` element in Docker compose files is obsolete and can be removed. See https://docs.docker.com/reference/compose-file/version-and-name/#version-top-level-element-obsolete --- docker-compose/docker-compose-benchmark.yaml | 1 - docker-compose/docker-compose-extras.yaml | 1 - src/ansys/acp/core/_server/docker-compose.yaml | 1 - 3 files changed, 3 deletions(-) diff --git a/docker-compose/docker-compose-benchmark.yaml b/docker-compose/docker-compose-benchmark.yaml index ffcfc8b854..5f3ad26450 100644 --- a/docker-compose/docker-compose-benchmark.yaml +++ b/docker-compose/docker-compose-benchmark.yaml @@ -1,4 +1,3 @@ -version: '3.8' services: acp-grpc-server: restart: unless-stopped diff --git a/docker-compose/docker-compose-extras.yaml b/docker-compose/docker-compose-extras.yaml index 5aad5fd323..ea943f925b 100644 --- a/docker-compose/docker-compose-extras.yaml +++ b/docker-compose/docker-compose-extras.yaml @@ -1,4 +1,3 @@ -version: '3.8' services: mapdl: restart: unless-stopped diff --git a/src/ansys/acp/core/_server/docker-compose.yaml b/src/ansys/acp/core/_server/docker-compose.yaml index b9c511cb18..4fc5287392 100644 --- a/src/ansys/acp/core/_server/docker-compose.yaml +++ b/src/ansys/acp/core/_server/docker-compose.yaml @@ -1,4 +1,3 @@ -version: '3.8' services: acp-grpc-server: restart: unless-stopped From 4d20b6da0629abae8c895f074a91668cf2c3b4bd Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 27 Nov 2024 08:43:24 +0100 Subject: [PATCH 78/96] Doc: add link to the Ansys Help Composites page (#708) Add a link to the main Composites page in the Ansys Help as a grid item card on the main page of the documentation. The reason for linking this page instead of a more specific page is that it is the only one where I could easily figure out how to link to a page that does not change URL with every release. Closes #707 --- doc/source/conf.py | 16 ++++++++++------ doc/source/index.rst | 6 ++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index a93f1bbdfa..ef8f70dc0c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -111,15 +111,19 @@ def _signature( SKIP_GALLERY = os.environ.get("PYACP_DOC_SKIP_GALLERY", "0").lower() in ("1", "true") SKIP_API = os.environ.get("PYACP_DOC_SKIP_API", "0").lower() in ("1", "true") +SOURCE_DIR = pathlib.Path(__file__).parent # nested example index files are directly included in the parent index file exclude_patterns = ["examples/*/index.rst"] if SKIP_API: - # Exclude all API documentation except the index - # The 'api/!(index).rst' syntax does not appear to be supported by Sphinx - for file_path in pathlib.Path("api").glob("**/*.rst"): - if str(file_path) != "api/index.rst": - exclude_patterns.append(str(file_path)) + # Exclude all API documentation except the top-level index file: + # Exclude files on the top level explicitly, except 'index.rst' + for file_path in (SOURCE_DIR / "api").iterdir(): + if not file_path.name == "index.rst": + pattern = str(file_path.relative_to(SOURCE_DIR).as_posix()) + exclude_patterns.append(pattern) + # Exclude all files in nested directories + exclude_patterns.append("api/**/*.rst") jinja_contexts = { @@ -201,7 +205,7 @@ def _signature( # "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None), "grpc": ("https://grpc.github.io/grpc/python/", None), "protobuf": ("https://googleapis.dev/python/protobuf/latest/", None), - "pyvista": ("https://docs.pyvista.org/version/stable", None), + "pyvista": ("https://docs.pyvista.org/", None), "ansys-dpf-core": ("https://dpf.docs.pyansys.com/version/stable/", None), "ansys-dpf-composites": ("https://composites.dpf.docs.pyansys.com/version/stable/", None), "ansys-mechanical-core": ("https://mechanical.docs.pyansys.com/version/stable/", None), diff --git a/doc/source/index.rst b/doc/source/index.rst index 680c30e994..86db66f7b2 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -70,6 +70,12 @@ optimization of composite structures. Information on how to contribute to PyACP. + .. grid-item-card:: :octicon:`mortar-board` ACP documentation + :link: https://ansyshelp.ansys.com/public/?returnurl=/Views/Secured/prod_page.html?pn=Composites&pid=CompositePrepPost + :columns: 12 + + Describes the modeling features and techniques of Ansys Composite PrepPost. + .. _limitations: From e36f836626eae9ca076c4072ce2d76e7f2fb3e0a Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 27 Nov 2024 08:56:12 +0100 Subject: [PATCH 79/96] Add CDB to PyMechanical example (#705) Add an example which loads the CDB file and ACPCompositeDefinitions.h5 into PyMechanical (after running PyACP), and then runs the analysis inside PyMechanical. PyDPF Composites is used for post-processing. Rename the mesh import helper for PyMechanical, since it can also be used for shells. Closes #702. --- ..._06-cdb-to-pymechanical-workflow_thumb.png | Bin 0 -> 28417 bytes .../api/mechanical_integration_helpers.rst | 2 +- .../04-pymechanical-solid-workflow.py | 2 +- .../06-cdb-to-pymechanical-workflow.py | 308 ++++++++++++++++++ .../core/mechanical_integration_helpers.py | 12 +- 5 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png create mode 100644 examples/workflows/06-cdb-to-pymechanical-workflow.py diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..ce181ec59d9c6b50ff7269132b17d6f49ceb45af GIT binary patch literal 28417 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 MsmWGKfBO3W06jWuMF0Q* literal 0 HcmV?d00001 diff --git a/doc/source/api/mechanical_integration_helpers.rst b/doc/source/api/mechanical_integration_helpers.rst index 4732b11628..784725952e 100644 --- a/doc/source/api/mechanical_integration_helpers.rst +++ b/doc/source/api/mechanical_integration_helpers.rst @@ -22,4 +22,4 @@ Mechanical integration helpers export_mesh_for_acp import_acp_composite_definitions - import_acp_solid_mesh + import_acp_mesh_from_cdb diff --git a/examples/workflows/04-pymechanical-solid-workflow.py b/examples/workflows/04-pymechanical-solid-workflow.py index 78fe3785c5..749ac96d0c 100644 --- a/examples/workflows/04-pymechanical-solid-workflow.py +++ b/examples/workflows/04-pymechanical-solid-workflow.py @@ -262,7 +262,7 @@ # # Import geometry, mesh, and named selections into Mechanical -pyacp.mechanical_integration_helpers.import_acp_solid_mesh( +pyacp.mechanical_integration_helpers.import_acp_mesh_from_cdb( mechanical=mechanical_solid_model, cdb_path=working_dir_path / solid_model_cdb_file ) diff --git a/examples/workflows/06-cdb-to-pymechanical-workflow.py b/examples/workflows/06-cdb-to-pymechanical-workflow.py new file mode 100644 index 0000000000..32257d5de7 --- /dev/null +++ b/examples/workflows/06-cdb-to-pymechanical-workflow.py @@ -0,0 +1,308 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +""" + +.. _cdb_to_pymechanical_example: + +CDB to PyMechanical shell workflow +================================== + +This example shows how to define a composite lay-up in PyACP based on a mesh +from a CDB file, import the model into PyMechanical for defining the load and +boundary conditions, and run a failure analysis with PyDPF Composites. + +""" + + +# %% +# Import modules and start the Ansys products +# ------------------------------------------- + + +# %% +# Import the standard library and third-party dependencies. + +from concurrent.futures import ThreadPoolExecutor +import pathlib +import tempfile +import textwrap + +# %% +# Import PyACP, PyMechanical, and PyDPF Composites. + +# isort: off +import ansys.acp.core as pyacp +from ansys.acp.core.extras import example_helpers +import ansys.dpf.composites as pydpf_composites +import ansys.mechanical.core as pymechanical + +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png' + +# %% +# Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` +# to start them in parallel. +with ThreadPoolExecutor() as executor: + futures = [ + executor.submit(pyacp.launch_acp), + executor.submit(pymechanical.launch_mechanical, batch=True), + executor.submit(pydpf_composites.server_helpers.connect_to_or_start_server), + ] + acp, mechanical, dpf = (fut.result() for fut in futures) + +# %% +# Get example input files +# ----------------------- +# +# Create a temporary working directory, and download the example input files +# to this directory. + +working_dir = tempfile.TemporaryDirectory() +working_dir_path = pathlib.Path(working_dir.name) +input_file = example_helpers.get_example_file( + example_helpers.ExampleKeys.BASIC_FLAT_PLATE_DAT, working_dir_path +) + +# %% +# Set up the ACP model +# -------------------- +# +# Setup basic ACP lay-up based on the CDB file. + + +model = acp.import_model(path=input_file, format="ansys:cdb") + +# %% +# Visualize the loaded mesh. +mesh = model.mesh.to_pyvista() +mesh.plot(show_edges=True) + + +# %% +# Define the composite lay-up +# --------------------------- +# +# Create an orthotropic material and fabric including strain limits, which are later +# used to postprocess the simulation. +engineering_constants = ( + pyacp.material_property_sets.ConstantEngineeringConstants.from_orthotropic_constants( + E1=5e10, E2=1e10, E3=1e10, nu12=0.28, nu13=0.28, nu23=0.3, G12=5e9, G23=4e9, G31=4e9 + ) +) + +strain_limit = 0.01 +strain_limits = pyacp.material_property_sets.ConstantStrainLimits.from_orthotropic_constants( + eXc=-strain_limit, + eYc=-strain_limit, + eZc=-strain_limit, + eXt=strain_limit, + eYt=strain_limit, + eZt=strain_limit, + eSxy=strain_limit, + eSyz=strain_limit, + eSxz=strain_limit, +) + +ud_material = model.create_material( + name="UD", + ply_type=pyacp.PlyType.REGULAR, + engineering_constants=engineering_constants, + strain_limits=strain_limits, +) + +fabric = model.create_fabric(name="UD", material=ud_material, thickness=0.1) + + +# %% +# Define a rosette and oriented selection set. Plot the orientation. +rosette = model.create_rosette(origin=(0.0, 0.0, 0.0), dir1=(1.0, 0.0, 0.0), dir2=(0.0, 0.0, 1.0)) + +oss = model.create_oriented_selection_set( + name="oss", + orientation_point=(0.0, 0.0, 0.0), + orientation_direction=(0.0, 1.0, 0), + element_sets=[model.element_sets["All_Elements"]], + rosettes=[rosette], +) + +model.update() + +plotter = pyacp.get_directions_plotter(model=model, components=[oss.elemental_data.orientation]) +plotter.show() + + +# %% +# Create various plies with different angles and add them to a modeling group. +modeling_group = model.create_modeling_group(name="modeling_group") +angles = [0, 45, -45, 45, -45, 0] +for idx, angle in enumerate(angles): + modeling_group.create_modeling_ply( + name=f"ply_{idx}_{angle}_{fabric.name}", + ply_angle=angle, + ply_material=fabric, + oriented_selection_sets=[oss], + ) + +model.update() + + +# %% +# Show the fiber directions of a specific ply. +modeling_ply = model.modeling_groups["modeling_group"].modeling_plies["ply_4_-45_UD"] + + +fiber_direction = modeling_ply.elemental_data.fiber_direction +assert fiber_direction is not None +plotter = pyacp.get_directions_plotter( + model=model, + components=[fiber_direction], +) + +plotter.show() + + +# %% +# For a quick overview, print the model tree. Note that +# the model can also be opened in the ACP GUI. For more +# information, see :ref:`view_the_model_in_the_acp_gui`. +pyacp.print_model(model) + +# %% +# Save the ACP model +# ------------------ + +cdb_filename = "model.cdb" +composite_definitions_h5_filename = "ACPCompositeDefinitions.h5" +matml_filename = "materials.xml" + +model.export_analysis_model(working_dir_path / cdb_filename) +model.export_shell_composite_definitions(working_dir_path / composite_definitions_h5_filename) +model.export_materials(working_dir_path / matml_filename) + + +# %% +# Import mesh, materials and plies into Mechanical +# ------------------------------------------------ +# +# Import geometry, mesh, and named selections into Mechanical + + +pyacp.mechanical_integration_helpers.import_acp_mesh_from_cdb( + mechanical=mechanical, cdb_path=working_dir_path / cdb_filename +) + + +# %% +# Import materials into Mechanical + +mechanical.run_python_script(f"Model.Materials.Import({str(working_dir_path / matml_filename)!r})") + +# %% +# Import plies into Mechanical + +pyacp.mechanical_integration_helpers.import_acp_composite_definitions( + mechanical=mechanical, path=working_dir_path / composite_definitions_h5_filename +) + +# %% +# Set boundary condition and solve +# --------------------------------- +# + +mechanical.run_python_script( + textwrap.dedent( + """\ + front_edge = Model.AddNamedSelection() + front_edge.Name = "Front Edge" + front_edge.ScopingMethod = GeometryDefineByType.Worksheet + + front_edge.GenerationCriteria.Add(None) + front_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + front_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + front_edge.GenerationCriteria[0].Operator = SelectionOperatorType.Largest + front_edge.Generate() + + back_edge = Model.AddNamedSelection() + back_edge.Name = "Back Edge" + back_edge.ScopingMethod = GeometryDefineByType.Worksheet + + back_edge.GenerationCriteria.Add(None) + back_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + back_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + back_edge.GenerationCriteria[0].Operator = SelectionOperatorType.Smallest + back_edge.Generate() + + analysis = Model.AddStaticStructuralAnalysis() + + fixed_support = analysis.AddFixedSupport() + fixed_support.Location = back_edge + + force = analysis.AddForce() + force.DefineBy = LoadDefineBy.Components + force.XComponent.Output.SetDiscreteValue(0, Quantity(1e6, "N")) + force.Location = front_edge + + analysis.Solution.Solve(True) + """ + ) +) + + +rst_file = [filename for filename in mechanical.list_files() if filename.endswith(".rst")][0] +matml_out = [filename for filename in mechanical.list_files() if filename.endswith("MatML.xml")][0] + +# %% +# Postprocess results +# ------------------- +# +# Evaluate the failure criteria using the PyDPF Composites. + + +max_strain = pydpf_composites.failure_criteria.MaxStrainCriterion() +cfc = pydpf_composites.failure_criteria.CombinedFailureCriterion( + name="Combined Failure Criterion", + failure_criteria=[max_strain], +) + +composite_model = pydpf_composites.composite_model.CompositeModel( + composite_files=pydpf_composites.data_sources.ContinuousFiberCompositesFiles( + rst=rst_file, + composite={ + "shell": pydpf_composites.data_sources.CompositeDefinitionFiles( + definition=working_dir_path / composite_definitions_h5_filename + ), + }, + engineering_data=working_dir_path / matml_out, + ), + server=dpf, +) + +# Evaluate the failure criteria +output_all_elements = composite_model.evaluate_failure_criteria(cfc) + +# Query and plot the results +irf_field = output_all_elements.get_field( + {"failure_label": pydpf_composites.constants.FailureOutput.FAILURE_VALUE} +) + +irf_field.plot() diff --git a/src/ansys/acp/core/mechanical_integration_helpers.py b/src/ansys/acp/core/mechanical_integration_helpers.py index f3de0b6bd4..319d1b657d 100644 --- a/src/ansys/acp/core/mechanical_integration_helpers.py +++ b/src/ansys/acp/core/mechanical_integration_helpers.py @@ -35,7 +35,7 @@ __all__ = [ "export_mesh_for_acp", "import_acp_composite_definitions", - "import_acp_solid_mesh", + "import_acp_mesh_from_cdb", ] @@ -67,11 +67,11 @@ def export_mesh_for_acp(*, mechanical: "pymechanical.Mechanical", path: PATH) -> ) -def import_acp_solid_mesh(*, mechanical: "pymechanical.Mechanical", cdb_path: PATH) -> None: - """Import an ACP solid model into Mechanical. +def import_acp_mesh_from_cdb(*, mechanical: "pymechanical.Mechanical", cdb_path: PATH) -> None: + """Import an ACP CDB mesh into Mechanical. - Import a solid mesh in CDB format into Mechanical. This function does not - import the ACP layup definition, use :func:`import_acp_composite_definitions` + Import a mesh exported from ACP in CDB format into Mechanical. This function + does not import the ACP layup definition, use :func:`import_acp_composite_definitions` for this purpose. .. warning:: @@ -126,7 +126,7 @@ def import_acp_composite_definitions(*, mechanical: "pymechanical.Mechanical", p Imports the composite layup defined in ACP into Mechanical, as Imported Plies. - This function does not import the solid mesh, use :func:`import_acp_solid_mesh` + This function does not import the solid mesh, use :func:`import_acp_mesh_from_cdb` for this purpose. Parameters From c34ff5323c01125d7cbd4afe6cab8394c3c7bcd3 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 27 Nov 2024 09:17:26 +0100 Subject: [PATCH 80/96] Add [plotting] extra when installing in nightly build (#706) The `plotting` extra needs to be installed to run the full unit test suite. This fixes the failure of the nightly CI build jobs. --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index fb38631895..cf2e43f215 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -32,10 +32,10 @@ jobs: python -m venv test_env . test_env/bin/activate - pip install $(echo dist/*.whl) + pip install $(echo dist/*.whl)[plotting] poetry config virtualenvs.create false --local - poetry install --only test + poetry install --no-root --only test --extras plotting - name: Login in Github Container registry uses: docker/login-action@v3 From 0ecbd0732dc802876dadc950bfc872217d0c2bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:02:30 +0100 Subject: [PATCH 81/96] Docs/imported solid model (#697) * add example for the definition of an imported solid model * refine refresh method of imported solid model and update doc string * add solid_mesh to SolidElementSet to support visualization --- .../021-imported-solid-model.py | 267 ++++++++++++++++++ .../acp/core/_tree_objects/cad_geometry.py | 8 +- .../_tree_objects/imported_solid_model.py | 16 +- .../core/_tree_objects/solid_element_set.py | 3 + src/ansys/acp/core/extras/example_helpers.py | 8 + tests/unittests/test_imported_solid_model.py | 16 +- tests/unittests/test_solid_element_set.py | 3 + 7 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 examples/modeling_features/021-imported-solid-model.py diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py new file mode 100644 index 0000000000..ab5de525c9 --- /dev/null +++ b/examples/modeling_features/021-imported-solid-model.py @@ -0,0 +1,267 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _imported_solid_model_example: + +Imported Solid model +==================== + +This example guides you through the definition of an :class:`.ImportedSolidModel` +which allows to map the layup onto an external solid mesh. +In contrast to the :class:`.SolidModel`, the raw solid mesh of +:class:`.ImportedSolidModel` is loaded from an external source, such as a CDB file. +In this example, the layup is applied onto a t-joint which consists of different +parts such as shell, stringer, and bonding skins. +The example only shows the PyACP part of the setup. For a complete composite analysis, +see :ref:`pymapdl_workflow_example`. + +This example starts from an ACP model with layup. It shows how to: + +- Create an :class:`.ImportedSolidModel` from an external mesh. +- Define the :class:`.LayupMappingObject` to apply the layup onto the solid mesh. +- Scope plies to specific parts of the solid mesh. +- Visualize the mapped layup. + +It is recommended to look at the Ansys help for all the details. This example shows the +basic setup only. +""" + +# %% +# Import the standard library and third-party dependencies. +import pathlib +import tempfile + +import pyvista + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import ElementTechnology, LayupMappingRosetteSelectionMethod, launch_acp +from ansys.acp.core.extras import ExampleKeys, get_example_file + +# sphinx_gallery_thumbnail_number = 3 + + +# %% +# Start ACP and load the model +# ---------------------------- +# %% +# Get the example file from the server. +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +input_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_ACPH5, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Load the model from an acph5 file +model = acp.import_model(input_file) + +# %% +# Import external solid model +# --------------------------- +# +# Get the solid mesh file and create an ImportedSolidModel, +# load the initial mesh and show the raw mesh without any mapping. +solid_mesh_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH, WORKING_DIR) +imported_solid_model = model.create_imported_solid_model( + name="Imported Solid Model", +) +imported_solid_model.refresh(path=solid_mesh_file, format="ansys:h5") +imported_solid_model.import_initial_mesh() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# The solid element sets are used as target for the mapping later. +# Here is the full list and one is visualized. +imported_solid_model.solid_element_sets.keys() + +solid_eset_mesh = imported_solid_model.solid_element_sets[ + "mapping_target bonding skin right" +].solid_mesh +plotter = pyvista.Plotter() +plotter.add_mesh(solid_eset_mesh.to_pyvista()) +plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +plotter.show() + +# %% +# Add mapping objects +# ------------------- +# +# Link the layup (plies) of the top skin of the sandwich +# with the corresponding named selections of the solid mesh +# and show the updated solid model. +solid_esets = imported_solid_model.solid_element_sets + +imported_solid_model.create_layup_mapping_object( + name="sandwich skin top", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets["els_sandwich_skin_top"]], + entire_solid_mesh=False, + solid_element_sets=[solid_esets["mapping_target sandwich skin top"]], +) + +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# Add other mapping objects +imported_solid_model.create_layup_mapping_object( + name="sandwich skin bottom", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets["els_sandwich_skin_bottom"]], + entire_solid_mesh=False, + solid_element_sets=[ + imported_solid_model.solid_element_sets["mapping_target sandwich skin bottom"] + ], +) + +imported_solid_model.create_layup_mapping_object( + name="stringer", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets["els_stringer_skin_left"]], + entire_solid_mesh=False, + solid_element_sets=[ + solid_esets[v] + for v in [ + "mapping_target stringer honeycomb", + "mapping_target stringer skin left", + "mapping_target stringer skin right", + ] + ], +) + +imported_solid_model.create_layup_mapping_object( + name="bonding skin", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[ + model.element_sets[v] for v in ["els_bonding_skin_left", "els_bonding_skin_right"] + ], + entire_solid_mesh=False, + solid_element_sets=[ + solid_esets[v] + for v in ["mapping_target bonding skin left", "mapping_target bonding skin right"] + ], +) + +# %% +# Show intermediate result +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# The mapping can also be done for specific plies +# as shown for the core materials. +imported_solid_model.create_layup_mapping_object( + name="foam", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[ + model.element_sets[v] for v in ["els_foam_core_left", "els_foam_core_right"] + ], + select_all_plies=False, + sequences=[model.modeling_groups["MG foam_core"]], + entire_solid_mesh=False, + solid_element_sets=[solid_esets["mapping_target foam core"]], + delete_lost_elements=False, + filler_material=model.materials["SAN Foam (81 kg m^-3)"], + rosettes=[model.rosettes["Global Coordinate System"]], + rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, +) + +imported_solid_model.create_layup_mapping_object( + name="honeycomb", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[ + model.element_sets[v] for v in ["els_honeycomb_left", "els_honeycomb_right"] + ], + select_all_plies=False, + sequences=[model.modeling_groups["MG honeycomb_core"]], + entire_solid_mesh=False, + solid_element_sets=[solid_esets["mapping_target sandwich honeycomb"]], + delete_lost_elements=False, + filler_material=model.materials["Honeycomb"], + rosettes=[model.rosettes["Global Coordinate System"]], + rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, +) +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# Add filler mapping objects where the solid mesh is "filled" +# with a single material. No plies from the layup are used here. + +imported_solid_model.create_layup_mapping_object( + name="resin", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[], + entire_solid_mesh=False, + solid_element_sets=[ + solid_esets[v] for v in ["mapping_target adhesive", "mapping_target adhesive stringer root"] + ], + delete_lost_elements=False, + filler_material=model.materials["Resin Epoxy"], + rosettes=[model.rosettes["Global Coordinate System"]], + rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, +) + +# %% +# Show final solid mesh with mapped layup +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + + +# %% +# Show extent and thickness of mapped plies +# ----------------------------------------- +# +# Use :func:`.print_model` to get the list of plies. After identifying the ply +# of interest, for example the thickness can be visualized. Note that +# only ply-wise data of :class:`.AnalysisPly` can be visualized on the +# solid mesh. :class:`.ProductionPly` and :class:`.ModelingPly` cannot +# be visualized on the solid mesh. +ap = ( + model.modeling_groups["MG bonding_skin_right"] + .modeling_plies["ModelingPly.26"] + .production_plies["ProductionPly.33"] + .analysis_plies["P1L1__ModelingPly.26"] +) +thickness_data = ap.elemental_data.thickness +thickness_pyvista_mesh = thickness_data.get_pyvista_mesh(mesh=ap.solid_mesh) # type: ignore +plotter = pyvista.Plotter() +plotter.add_mesh(thickness_pyvista_mesh) +plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +plotter.show() + + +# %% +# Other features +# -------------- +# +# The :class:`.CutOffGeometry` can be used in combination witt the :class:`.ImportedSolidModel` +# as well. See example :ref:`solid_model_example` for more details. +# More plotting capabilities are shown in the example :ref:`solid_model_example` as well. +# +# The solid mesh can be exported as CDB for MAPDL or to PyMechanical for further analysis. +# These workflows are shown in :ref:`pymapdl_workflow_example` and +# :ref:`pymechanical_solid_example`. diff --git a/src/ansys/acp/core/_tree_objects/cad_geometry.py b/src/ansys/acp/core/_tree_objects/cad_geometry.py index 04762d901a..a801b6d3f3 100644 --- a/src/ansys/acp/core/_tree_objects/cad_geometry.py +++ b/src/ansys/acp/core/_tree_objects/cad_geometry.py @@ -180,7 +180,13 @@ def visualization_mesh(self) -> TriangleMesh: ) def refresh(self, path: PATH) -> None: - """Reload the geometry from its external source.""" + """Reload the geometry from its external source. + + Parameters + ---------- + path : + Path of the new input file. + """ self.external_path = self._server_wrapper.auto_upload(path) stub = cast(cad_geometry_pb2_grpc.ObjectServiceStub, self._get_stub()) with wrap_grpc_errors(): diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index bca3e1ec96..55f534b985 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -354,8 +354,20 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: layup_mapping_object_pb2_grpc.ObjectServiceStub, ) - def refresh(self, path: _PATH) -> None: - """Re-import the solid model from the external file.""" + def refresh(self, path: _PATH, format: SolidModelImportFormat | None = None) -> None: # type: ignore + """ + Re-import the solid model from the external file. + + Parameters + ---------- + path : + Path of the new input file. + format : + Switch format of the input file. Optional, uses the current format of the + imported solid model if not specified. + """ + if format is not None: + self.format = format self.external_path = self._server_wrapper.auto_upload(path) with wrap_grpc_errors(): self._get_stub().Refresh( # type: ignore diff --git a/src/ansys/acp/core/_tree_objects/solid_element_set.py b/src/ansys/acp/core/_tree_objects/solid_element_set.py index cafbf1d6c2..501beee85b 100644 --- a/src/ansys/acp/core/_tree_objects/solid_element_set.py +++ b/src/ansys/acp/core/_tree_objects/solid_element_set.py @@ -31,6 +31,7 @@ from .._utils.property_protocols import ReadOnlyProperty from ._elemental_or_nodal_data import ElementalData, NodalData from ._grpc_helpers.property_helper import grpc_data_property_read_only, mark_grpc_properties +from ._mesh_data import solid_mesh_property from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -76,3 +77,5 @@ def _create_stub(self) -> solid_element_set_pb2_grpc.ObjectServiceStub: element_labels: ReadOnlyProperty[tuple[int, ...]] = grpc_data_property_read_only( "properties.element_labels", from_protobuf=to_tuple_from_1D_array ) + + solid_mesh = solid_mesh_property diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index aa67ae47ae..cc59e277b3 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -67,6 +67,8 @@ class ExampleKeys(Enum): CLASS40_AGDB = auto() CLASS40_CDB = auto() MATERIALS_XML = auto() + IMPORTED_SOLID_MODEL_ACPH5 = auto() + IMPORTED_SOLID_MODEL_SOLID_MESH = auto() SNAP_TO_GEOMETRY = auto() CUT_OFF_GEOMETRY_SOLID_MODEL = auto() @@ -102,6 +104,12 @@ class ExampleKeys(Enum): ExampleKeys.CLASS40_AGDB: _ExampleLocation(directory="class40", filename="class40.agdb"), ExampleKeys.CLASS40_CDB: _ExampleLocation(directory="class40", filename="class40.cdb"), ExampleKeys.MATERIALS_XML: _ExampleLocation(directory="materials", filename="materials.engd"), + ExampleKeys.IMPORTED_SOLID_MODEL_ACPH5: _ExampleLocation( + directory="imported_solid_model", filename="t-joint-ACP-Pre.acph5" + ), + ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH: _ExampleLocation( + directory="imported_solid_model", filename="t-joint.solid.h5" + ), ExampleKeys.SNAP_TO_GEOMETRY: _ExampleLocation( directory="geometries", filename="snap_to_geometry.stp" ), diff --git a/tests/unittests/test_imported_solid_model.py b/tests/unittests/test_imported_solid_model.py index f676459ea9..16e7229880 100644 --- a/tests/unittests/test_imported_solid_model.py +++ b/tests/unittests/test_imported_solid_model.py @@ -250,11 +250,21 @@ def test_import_initial_mesh(acp_instance, parent_object): model.update() with tempfile.TemporaryDirectory() as tmp_dir: - out_path = pathlib.Path(tmp_dir) / f"out_file.h5" - solid_model.export(path=out_path, format=pyacp.SolidModelExportFormat.ANSYS_H5) + out_path_h5 = pathlib.Path(tmp_dir) / f"out_file.h5" + solid_model.export(path=out_path_h5, format=pyacp.SolidModelExportFormat.ANSYS_H5) + out_path_cdb = pathlib.Path(tmp_dir) / f"out_file.cdb" + solid_model.export(path=out_path_cdb, format=pyacp.SolidModelExportFormat.ANSYS_CDB) imported_solid_model = model.create_imported_solid_model( - external_path=acp_instance.upload_file(out_path), + external_path=acp_instance.upload_file(out_path_h5), format=pyacp.SolidModelImportFormat.ANSYS_H5, ) imported_solid_model.import_initial_mesh() + + # refresh from external source with the same format + imported_solid_model.refresh(out_path_h5) + imported_solid_model.import_initial_mesh() + + # refresh from external source where the format is different + imported_solid_model.refresh(out_path_cdb, format=pyacp.SolidModelImportFormat.ANSYS_CDB) + imported_solid_model.import_initial_mesh() diff --git a/tests/unittests/test_solid_element_set.py b/tests/unittests/test_solid_element_set.py index 82e4556d30..c277288cdd 100644 --- a/tests/unittests/test_solid_element_set.py +++ b/tests/unittests/test_solid_element_set.py @@ -89,3 +89,6 @@ def test_properties(self, parent_object, properties): ref_values = properties[solid_element_set.id] for prop, value in ref_values.items(): assert getattr(solid_element_set, prop) == value + + assert solid_element_set.solid_mesh is not None + assert solid_element_set.solid_mesh.element_labels == (2,) From 27b839570a42e855193673eac7248a473b8e07a0 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 28 Nov 2024 08:14:43 +0100 Subject: [PATCH 82/96] Document the limitation of the mechanical composite failure tool (#713) Closes #701. --- doc/source/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 86db66f7b2..b8445650dc 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -93,6 +93,8 @@ Limitations * The ``ansys.acp.core.mechanical_integration_helpers`` module may be changed or removed in future versions, when the corresponding features are available in PyMechanical directly. + * Post-processing with the Composite Failure Tool inside Mechanical is not + yet supported. * Visualization and mesh data of imported plies are not supported yet. * Section cuts cannot be visualized. From 2ff380a01b259a8d71c97f188add0be32ddd2106 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 28 Nov 2024 08:27:55 +0100 Subject: [PATCH 83/96] Minor tweaks to PyMechanical workflow examples (#712) - Add note about why the PyMechanical shell workflow and PyMechanical to CDB workflow results are not exactly equal - Remove TODO about material import; I think the current example code is ok - Add some comments to Mechanical code - Make values in CDB to PyMechanical example more reasonable Checked manually that the results match with an equivalent workflow set up in WB. Closes #703. --- ..._06-cdb-to-pymechanical-workflow_thumb.png | Bin 28417 -> 28881 bytes .../03-pymechanical-shell-workflow.py | 5 ++++- .../04-pymechanical-solid-workflow.py | 2 +- .../05-pymechanical-to-cdb-workflow.py | 7 +++++++ .../06-cdb-to-pymechanical-workflow.py | 5 +++-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png index ce181ec59d9c6b50ff7269132b17d6f49ceb45af..128e7340feb19b797007f81c67cb29cf209f6ee2 100644 GIT binary patch literal 28881 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 MsmWGKfBO3W06jWuMF0Q* diff --git a/examples/workflows/03-pymechanical-shell-workflow.py b/examples/workflows/03-pymechanical-shell-workflow.py index faf7997182..677e1a95fa 100644 --- a/examples/workflows/03-pymechanical-shell-workflow.py +++ b/examples/workflows/03-pymechanical-shell-workflow.py @@ -105,6 +105,7 @@ # This script runs in the Mechanical Python environment, which uses IronPython 2.7. textwrap.dedent( f"""\ + # Import the geometry geometry_import = Model.GeometryImportGroup.AddGeometryImport() import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic @@ -119,6 +120,8 @@ import_preferences ) + # The thickness will be overridden by the ACP model, but is required + # for the model to be valid. for body in Model.Geometry.GetChildren( Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True ): @@ -138,7 +141,7 @@ # definition file to output_path. composite_definitions_h5 = "ACPCompositeDefinitions.h5" -matml_file = "materials.xml" # TODO: load an example materials XML file instead of defining the materials in ACP +matml_file = "materials.xml" model = acp.import_model(mesh_path, format="ansys:h5") diff --git a/examples/workflows/04-pymechanical-solid-workflow.py b/examples/workflows/04-pymechanical-solid-workflow.py index 749ac96d0c..fe142634c1 100644 --- a/examples/workflows/04-pymechanical-solid-workflow.py +++ b/examples/workflows/04-pymechanical-solid-workflow.py @@ -180,7 +180,7 @@ # - Solid model composite definitions HDF5 file # - Solid model CDB file -matml_file = "materials.xml" # TODO: load an example materials XML file instead of defining the materials in ACP +matml_file = "materials.xml" solid_model_cdb_file = "SolidModel.cdb" solid_model_composite_definitions_h5 = "SolidModel.h5" diff --git a/examples/workflows/05-pymechanical-to-cdb-workflow.py b/examples/workflows/05-pymechanical-to-cdb-workflow.py index 5df0e4535e..2089016be5 100644 --- a/examples/workflows/05-pymechanical-to-cdb-workflow.py +++ b/examples/workflows/05-pymechanical-to-cdb-workflow.py @@ -123,6 +123,8 @@ ): body.Thickness = Quantity(1e-6, "m") + Model.Mesh.GenerateMesh() + # Define named selections at the front and back edges front_edge = Model.AddNamedSelection() front_edge.Name = "Front Edge" @@ -303,6 +305,11 @@ # %% # Query and plot the results. +# +# Note that the maximum IRF is different when compared to :ref:`pymechanical_shell_example` +# because ACP sets the ``ERESX,NO`` option in the CDB file. This option disables interpolation +# of the results from the integration point to the nodes. + irf_field = output_all_elements.get_field( {"failure_label": pydpf_composites.constants.FailureOutput.FAILURE_VALUE} ) diff --git a/examples/workflows/06-cdb-to-pymechanical-workflow.py b/examples/workflows/06-cdb-to-pymechanical-workflow.py index 32257d5de7..9beb625729 100644 --- a/examples/workflows/06-cdb-to-pymechanical-workflow.py +++ b/examples/workflows/06-cdb-to-pymechanical-workflow.py @@ -91,6 +91,7 @@ model = acp.import_model(path=input_file, format="ansys:cdb") +model.unit_system # %% # Visualize the loaded mesh. @@ -130,7 +131,7 @@ strain_limits=strain_limits, ) -fabric = model.create_fabric(name="UD", material=ud_material, thickness=0.1) +fabric = model.create_fabric(name="UD", material=ud_material, thickness=1e-4) # %% @@ -259,7 +260,7 @@ force = analysis.AddForce() force.DefineBy = LoadDefineBy.Components - force.XComponent.Output.SetDiscreteValue(0, Quantity(1e6, "N")) + force.XComponent.Output.SetDiscreteValue(0, Quantity(100, "N")) force.Location = front_edge analysis.Solution.Solve(True) From d440904ba14e2ade888e3a901a216d8e43f025b0 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 28 Nov 2024 09:35:44 +0100 Subject: [PATCH 84/96] Fix tree printer to include all object types (#679) Change the implementation of 'get_model_tree' to use the '_GRPC_PROPERTIES' class attribute, and thus include all newly-added object types in the representation. The style of the tree has changed in some ways: - The root node now shows the model name, instead of always 'Model' - Objects (name or id) are distinguished from collections by wrapping the string in single quotes - The additional nesting level for 'Materials', 'Selection Rules', and other 'logical' groupings which are not part of the PyACP hierarchy has been removed The new tree structure can be seen in the updated test cases, or in `print_model.rst`. Partially addresses #348, interactive tree support in Jupyter notebooks is still missing. --- doc/source/user_guide/howto/print_model.rst | 93 +++++++--- src/ansys/acp/core/_model_printer.py | 113 ++++-------- tests/unittests/test_tree_printer.py | 191 ++++++++++---------- 3 files changed, 209 insertions(+), 188 deletions(-) diff --git a/doc/source/user_guide/howto/print_model.rst b/doc/source/user_guide/howto/print_model.rst index 658bd3f7a7..8eaa709f90 100644 --- a/doc/source/user_guide/howto/print_model.rst +++ b/doc/source/user_guide/howto/print_model.rst @@ -21,28 +21,27 @@ You can print the tree structure using the :func:`.print_model` function: .. doctest:: >>> pyacp.print_model(model) - Model - Material Data - Materials - Structural Steel - Fabrics - Fabric.1 + 'ACP Model' + Materials + 'Structural Steel' + Fabrics + 'Fabric.1' Element Sets - All_Elements + 'All_Elements' Edge Sets - ns_edge - Geometry + 'ns_edge' Rosettes - Global Coordinate System - Lookup Tables - Selection Rules + 'Global Coordinate System' Oriented Selection Sets - OrientedSelectionSet.1 + 'OrientedSelectionSet.1' Modeling Groups - ModelingGroup.1 - ModelingPly.1 - ProductionPly - P1L1__ModelingPly.1 + 'ModelingGroup.1' + Modeling Plies + 'ModelingPly.1' + Production Plies + 'P1__ModelingPly.1' + Analysis Plies + 'P1L1__ModelingPly.1' @@ -52,16 +51,66 @@ Alternatively, you can use :func:`.get_model_tree` to get a tree representation. >>> tree_root = pyacp.get_model_tree(model) >>> tree_root.label - 'Model' + "'ACP Model'" >>> for child in tree_root.children: ... print(child.label) ... - Material Data + Materials + Fabrics Element Sets Edge Sets - Geometry Rosettes - Lookup Tables - Selection Rules Oriented Selection Sets Modeling Groups + + +The ``hide_empty`` label can be set to ``False`` to also show empty groups: + +.. doctest:: + + >>> pyacp.print_model(model, hide_empty=False) + 'ACP Model' + Materials + 'Structural Steel' + Fabrics + 'Fabric.1' + Stackups + Sublaminates + Element Sets + 'All_Elements' + Edge Sets + 'ns_edge' + Cad Geometries + Virtual Geometries + Rosettes + 'Global Coordinate System' + Lookup Tables 1d + Lookup Tables 3d + Parallel Selection Rules + Cylindrical Selection Rules + Spherical Selection Rules + Tube Selection Rules + Cutoff Selection Rules + Geometrical Selection Rules + Variable Offset Selection Rules + Boolean Selection Rules + Oriented Selection Sets + 'OrientedSelectionSet.1' + Modeling Groups + 'ModelingGroup.1' + Modeling Plies + 'ModelingPly.1' + Production Plies + 'P1__ModelingPly.1' + Analysis Plies + 'P1L1__ModelingPly.1' + Interface Layers + Butt Joint Sequences + Imported Modeling Groups + Sampling Points + Section Cuts + Solid Models + Imported Solid Models + Sensors + Field Definitions + diff --git a/src/ansys/acp/core/_model_printer.py b/src/ansys/acp/core/_model_printer.py index aac0847418..79fa82f6b1 100644 --- a/src/ansys/acp/core/_model_printer.py +++ b/src/ansys/acp/core/_model_printer.py @@ -22,6 +22,8 @@ import os +from ._tree_objects._grpc_helpers.mapping import Mapping +from ._tree_objects.base import TreeObjectBase from ._tree_objects.model import Model from ._utils.string_manipulation import replace_underscores_and_capitalize @@ -52,95 +54,56 @@ def __str__(self, level: int | None = 0) -> str: return ret -def _add_tree_part( - tree: Node, - container_name: str, - model: Model, -) -> None: - items = list(getattr(model, container_name).items()) - if len(items) == 0: - return - container = Node(replace_underscores_and_capitalize(container_name)) - tree.children.append(container) - for entity_name, entity in items: - group_node = Node(entity_name) - container.children.append(group_node) - - -def print_model(model: Model) -> None: +def print_model(model: Model, *, hide_empty: bool = True) -> None: """Print a tree representation of the model. Parameters ---------- model: pyACP model + hide_empty : + Whether to hide empty collections. """ - return print(get_model_tree(model)) + return print(get_model_tree(model, hide_empty=hide_empty)) -def get_model_tree(model: Model) -> Node: +def get_model_tree(model: Model, *, hide_empty: bool = True) -> Node: """Get a tree representation of the model. Returns the root node. Parameters ---------- - model: - pyACP model. + model : + ACP model. + hide_empty : + Whether to hide empty collections. """ - model_node = Node("Model") - - material_data = Node("Material Data") - model_node.children.append(material_data) - _add_tree_part(material_data, "materials", model) - _add_tree_part(material_data, "fabrics", model) - _add_tree_part(material_data, "stackups", model) - _add_tree_part(material_data, "sublaminates", model) - - _add_tree_part(model_node, "element_sets", model) - _add_tree_part(model_node, "edge_sets", model) - - geometry = Node("Geometry") - model_node.children.append(geometry) - _add_tree_part(geometry, "cad_geometries", model) - _add_tree_part(geometry, "virtual_geometries", model) - - _add_tree_part(model_node, "rosettes", model) - - lookup_table = Node("Lookup Tables") - model_node.children.append(lookup_table) - _add_tree_part(lookup_table, "lookup_tables_1d", model) - _add_tree_part(lookup_table, "lookup_tables_3d", model) - - selection_rules = Node("Selection Rules") - model_node.children.append(selection_rules) - _add_tree_part(selection_rules, "parallel_selection_rules", model) - _add_tree_part(selection_rules, "cylindrical_selection_rules", model) - _add_tree_part(selection_rules, "spherical_selection_rules", model) - _add_tree_part(selection_rules, "tube_selection_rules", model) - _add_tree_part(selection_rules, "cutoff_selection_rules", model) - _add_tree_part(selection_rules, "geometrical_selection_rules", model) - _add_tree_part(selection_rules, "variable_offset_selection_rules", model) - _add_tree_part(selection_rules, "boolean_selection_rules", model) - - _add_tree_part(model_node, "oriented_selection_sets", model) - - modeling_groups = Node("Modeling Groups") - model_node.children.append(modeling_groups) - for modeling_group_name, modeling_group in model.modeling_groups.items(): - group_node = Node(modeling_group_name) - modeling_groups.children.append(group_node) - for modeling_ply_name, modeling_ply in modeling_group.modeling_plies.items(): - modeling_ply_node = Node(modeling_ply_name) - group_node.children.append(modeling_ply_node) - for production_ply_name, production_ply in modeling_ply.production_plies.items(): - production_ply_node = Node(production_ply_name) - modeling_ply_node.children.append(production_ply_node) - for analysis_ply_name, analysis_ply in production_ply.analysis_plies.items(): - analysis_ply_node = Node(analysis_ply_name) - production_ply_node.children.append(analysis_ply_node) - - _add_tree_part(model_node, "sensors", model) - - return model_node + return _get_model_tree_impl(obj=model, hide_empty=hide_empty) + + +def _get_model_tree_impl(obj: TreeObjectBase, *, hide_empty: bool) -> Node: + obj_node = Node(repr(_name_or_id(obj))) + for attr_name in obj._GRPC_PROPERTIES: + try: + attr = getattr(obj, attr_name) + except (AttributeError, RuntimeError): + continue + if isinstance(attr, Mapping): + collection_node = Node(replace_underscores_and_capitalize(attr_name)) + obj_node.children.append(collection_node) + for child_obj in attr.values(): + collection_node.children.append( + _get_model_tree_impl(child_obj, hide_empty=hide_empty) + ) + if hide_empty and not collection_node.children: + obj_node.children.pop() + return obj_node + + +def _name_or_id(obj: TreeObjectBase) -> str: + try: + return obj.name + except AttributeError: + return obj.id # type: ignore diff --git a/tests/unittests/test_tree_printer.py b/tests/unittests/test_tree_printer.py index 198762c87a..ad53f495e6 100644 --- a/tests/unittests/test_tree_printer.py +++ b/tests/unittests/test_tree_printer.py @@ -21,50 +21,50 @@ # SOFTWARE. import os +import textwrap + +from pytest_cases import parametrize_with_cases from ansys.acp.core import get_model_tree -def test_printed_model(acp_instance, model_data_dir): - """ - Test that model tree looks correct. - """ +def case_simple_model(acp_instance, model_data_dir): input_file_path = model_data_dir / "minimal_complete_model_no_matml_link.acph5" model = acp_instance.import_model(name="minimal_complete", path=input_file_path) - model.update() - tree = get_model_tree(model) - - assert ( - os.linesep + str(tree) - == """ -Model - Material Data - Materials - Structural Steel - Fabrics - Fabric.1 - Element Sets - All_Elements - Edge Sets - ns_edge - Geometry - Rosettes - Global Coordinate System - Lookup Tables - Selection Rules - Oriented Selection Sets - OrientedSelectionSet.1 - Modeling Groups - ModelingGroup.1 - ModelingPly.1 - ProductionPly - P1L1__ModelingPly.1 -""".replace( - "\n", os.linesep - ) + return model, textwrap.dedent( + """\ + 'minimal_complete' + Materials + 'Structural Steel' + Fabrics + 'Fabric.1' + Element Sets + 'All_Elements' + Edge Sets + 'ns_edge' + Rosettes + 'Global Coordinate System' + Oriented Selection Sets + 'OrientedSelectionSet.1' + Modeling Groups + 'ModelingGroup.1' + Modeling Plies + 'ModelingPly.1' + Production Plies + 'P1__ModelingPly.1' + Analysis Plies + 'P1L1__ModelingPly.1' + """ ) + +def case_more_objects(acp_instance, model_data_dir): + input_file_path = model_data_dir / "minimal_complete_model_no_matml_link.acph5" + model = acp_instance.import_model(name="minimal_complete", path=input_file_path) + + model.update() + model.create_edge_set() model.create_stackup() model.create_sublaminate() @@ -80,61 +80,70 @@ def test_printed_model(acp_instance, model_data_dir): model.create_lookup_table_3d() model.create_sensor() + return model, textwrap.dedent( + """\ + 'minimal_complete' + Materials + 'Structural Steel' + Fabrics + 'Fabric.1' + Stackups + 'Stackup' + Sublaminates + 'SubLaminate' + Element Sets + 'All_Elements' + Edge Sets + 'ns_edge' + 'EdgeSet' + Cad Geometries + 'CADGeometry' + Virtual Geometries + 'VirtualGeometry' + Rosettes + 'Global Coordinate System' + Lookup Tables 1d + 'LookUpTable1D' + Columns + 'Location' + Lookup Tables 3d + 'LookUpTable3D' + Columns + 'Location' + Parallel Selection Rules + 'ParallelSelectionrule' + Cylindrical Selection Rules + 'CylindricalSelectionrule' + Tube Selection Rules + 'TubeSelectionrule' + Cutoff Selection Rules + 'CutoffSelectionrule' + Geometrical Selection Rules + 'GeometricalSelectionrule' + Boolean Selection Rules + 'BooleanSelectionrule' + Oriented Selection Sets + 'OrientedSelectionSet.1' + Modeling Groups + 'ModelingGroup.1' + Modeling Plies + 'ModelingPly.1' + Production Plies + 'P1__ModelingPly.1' + Analysis Plies + 'P1L1__ModelingPly.1' + Sensors + 'Sensor' + """ + ) + + +@parametrize_with_cases("model,expected", cases=".", glob="*") +def test_printed_model(model, expected): + """ + Test that model tree looks correct. + """ + tree = get_model_tree(model) - assert ( - os.linesep + str(tree) - == """ -Model - Material Data - Materials - Structural Steel - Fabrics - Fabric.1 - Stackups - Stackup - Sublaminates - SubLaminate - Element Sets - All_Elements - Edge Sets - ns_edge - EdgeSet - Geometry - Cad Geometries - CADGeometry - Virtual Geometries - VirtualGeometry - Rosettes - Global Coordinate System - Lookup Tables - Lookup Tables 1d - LookUpTable1D - Lookup Tables 3d - LookUpTable3D - Selection Rules - Parallel Selection Rules - ParallelSelectionrule - Cylindrical Selection Rules - CylindricalSelectionrule - Tube Selection Rules - TubeSelectionrule - Cutoff Selection Rules - CutoffSelectionrule - Geometrical Selection Rules - GeometricalSelectionrule - Boolean Selection Rules - BooleanSelectionrule - Oriented Selection Sets - OrientedSelectionSet.1 - Modeling Groups - ModelingGroup.1 - ModelingPly.1 - ProductionPly - P1L1__ModelingPly.1 - Sensors - Sensor -""".replace( - "\n", os.linesep - ) - ) + assert str(tree) == expected.replace("\n", os.linesep) From 799a674d483becbb68ec0d0d11cbfa81f685f962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:56:43 +0100 Subject: [PATCH 85/96] Add example for Imported Plies and the hdf5 composite cae interface (#714) * Add examples for Imported Plies HDF5 composite CAE * Add solid_mesh property to ImportedAnalysisPly and ImportedSolidModel --------- Co-authored-by: Dominik Gresch --- .../01-sandwich-panel-layup.py | 4 +- .../02-simple-selection-rules.py | 4 +- examples/modeling_features/020-solid_model.py | 40 +-- .../021-imported-solid-model.py | 7 +- .../03-advanced-selection-rules.py | 4 +- .../04-layup-thickness-definitions.py | 8 +- .../05-rosettes-ply-directions.py | 4 +- .../modeling_features/050-composite_cae_h5.py | 238 +++++++++++++++++ .../06-ply-direction-lookup-table.py | 4 +- .../modeling_features/07-imported-plies.py | 245 ++++++++++++++++++ .../_tree_objects/imported_analysis_ply.py | 3 + .../_tree_objects/imported_solid_model.py | 3 + src/ansys/acp/core/extras/__init__.py | 9 +- src/ansys/acp/core/extras/example_helpers.py | 21 ++ tests/unittests/test_imported_solid_model.py | 4 + 15 files changed, 564 insertions(+), 34 deletions(-) create mode 100644 examples/modeling_features/050-composite_cae_h5.py create mode 100644 examples/modeling_features/07-imported-plies.py diff --git a/examples/modeling_features/01-sandwich-panel-layup.py b/examples/modeling_features/01-sandwich-panel-layup.py index 3d19baf950..b2d37ea992 100644 --- a/examples/modeling_features/01-sandwich-panel-layup.py +++ b/examples/modeling_features/01-sandwich-panel-layup.py @@ -23,8 +23,8 @@ """ .. _sandwich_panel: -Sandwich panel example -====================== +Sandwich panel +============== This example defines a composite lay-up for a sandwich panel using PyACP. It only shows the PyACP part of the setup. For a complete composite analysis, diff --git a/examples/modeling_features/02-simple-selection-rules.py b/examples/modeling_features/02-simple-selection-rules.py index 759caa7b85..5bb07793e4 100644 --- a/examples/modeling_features/02-simple-selection-rules.py +++ b/examples/modeling_features/02-simple-selection-rules.py @@ -23,8 +23,8 @@ """ .. _basic_selection_rules_example: -Basic selection rules example -============================= +Basic selection rules +===================== This example shows the basic usage of selection rules, which enable you to select elements through geometrical operations and thus to shape plies. The example only shows the PyACP part of the setup. diff --git a/examples/modeling_features/020-solid_model.py b/examples/modeling_features/020-solid_model.py index 9a62941a45..44f42335e7 100644 --- a/examples/modeling_features/020-solid_model.py +++ b/examples/modeling_features/020-solid_model.py @@ -52,7 +52,7 @@ get_directions_plotter, launch_acp, ) -from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.extras import FLAT_PLATE_SOLID_CAMERA, ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 4 @@ -92,7 +92,23 @@ ) model.update() -model.solid_mesh.to_pyvista().plot(show_edges=True) + + +def plot_model_with_geometry(cad_geometry: CADGeometry | None, cad_geom_opacity: float = 0.1): + """Plot solid model and geometry.""" + plotter = pyvista.Plotter() + if cad_geometry: + geom_mesh = cad_geometry.visualization_mesh.to_pyvista() + plotter.add_mesh(geom_mesh, color="green", opacity=cad_geom_opacity) + edges = geom_mesh.extract_feature_edges() + plotter.add_mesh(edges, color="white", line_width=4) + plotter.add_mesh(edges, color="black", line_width=2) + plotter.add_mesh(model.solid_mesh.to_pyvista(), show_edges=True) + plotter.camera_position = FLAT_PLATE_SOLID_CAMERA + plotter.show() + + +plot_model_with_geometry(None) def create_virtual_geometry_from_file( @@ -109,18 +125,6 @@ def create_virtual_geometry_from_file( return geometry_obj, virtual_geometry -def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float = 0.1): - """Plot solid model and geometry.""" - plotter = pyvista.Plotter() - geom_mesh = cad_geometry.visualization_mesh.to_pyvista() - plotter.add_mesh(geom_mesh, color="green", opacity=cad_geom_opacity) - edges = geom_mesh.extract_feature_edges() - plotter.add_mesh(edges, color="white", line_width=4) - plotter.add_mesh(edges, color="black", line_width=2) - plotter.add_mesh(model.solid_mesh.to_pyvista(), show_edges=True) - plotter.show() - - # %% # Snap the top to a geometry # -------------------------- @@ -160,7 +164,7 @@ def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float depth=0.6, ) model.update() -model.solid_mesh.to_pyvista().plot(show_edges=True) +plot_model_with_geometry(None) # %% # Cut-off an edge @@ -208,7 +212,8 @@ def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float length_factor=10.0, culling_factor=10, ) -direction_plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=True) +direction_plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +direction_plotter.camera_position = FLAT_PLATE_SOLID_CAMERA direction_plotter.show() # %% @@ -218,5 +223,6 @@ def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float thickness_pyvista_mesh = thickness_data.get_pyvista_mesh(mesh=ap.solid_mesh) # type: ignore plotter = pyvista.Plotter() plotter.add_mesh(thickness_pyvista_mesh) -plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=True) +plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +plotter.camera_position = FLAT_PLATE_SOLID_CAMERA plotter.show() diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index ab5de525c9..c14cd7c86f 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -23,7 +23,7 @@ """ .. _imported_solid_model_example: -Imported Solid model +Imported solid model ==================== This example guides you through the definition of an :class:`.ImportedSolidModel` @@ -58,7 +58,7 @@ from ansys.acp.core import ElementTechnology, LayupMappingRosetteSelectionMethod, launch_acp from ansys.acp.core.extras import ExampleKeys, get_example_file -# sphinx_gallery_thumbnail_number = 3 +# sphinx_gallery_thumbnail_number = 4 # %% @@ -262,6 +262,9 @@ # as well. See example :ref:`solid_model_example` for more details. # More plotting capabilities are shown in the example :ref:`solid_model_example` as well. # +# An example of an :class:`.ImportedSolidModel` in combination with :class:`.ImportedModelingPly` +# is shown in :ref:`imported_plies_example`. +# # The solid mesh can be exported as CDB for MAPDL or to PyMechanical for further analysis. # These workflows are shown in :ref:`pymapdl_workflow_example` and # :ref:`pymechanical_solid_example`. diff --git a/examples/modeling_features/03-advanced-selection-rules.py b/examples/modeling_features/03-advanced-selection-rules.py index 17a0743697..2386f796fe 100644 --- a/examples/modeling_features/03-advanced-selection-rules.py +++ b/examples/modeling_features/03-advanced-selection-rules.py @@ -23,8 +23,8 @@ """ .. _advanced_selection_rules_example: -Advanced selection rules example -================================ +Advanced selection rules +======================== This example shows how to use advanced rules, including the geometrical, cut-off, and variable offset rules. It also demonstrates how rules can be templated diff --git a/examples/modeling_features/04-layup-thickness-definitions.py b/examples/modeling_features/04-layup-thickness-definitions.py index b0a3884e70..b5db53c001 100644 --- a/examples/modeling_features/04-layup-thickness-definitions.py +++ b/examples/modeling_features/04-layup-thickness-definitions.py @@ -23,8 +23,8 @@ """ .. _thickness_definition_example: -Thickness definition example -============================ +Thickness definition +==================== This example shows how the thickness of a ply can be defined by a geometry or a lookup table. The example only shows the PyACP part of the setup. For a complete composite analysis, @@ -46,7 +46,7 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import DimensionType, ThicknessType, launch_acp -from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.extras import FLAT_PLATE_SOLID_CAMERA, ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 2 @@ -117,7 +117,7 @@ plotter.add_mesh(edges, color="black", line_width=2) # Plot the ply thickness plotter.add_mesh(model.elemental_data.thickness.get_pyvista_mesh(mesh=model.mesh), show_edges=True) - +plotter.camera_position = FLAT_PLATE_SOLID_CAMERA plotter.show() # %% diff --git a/examples/modeling_features/05-rosettes-ply-directions.py b/examples/modeling_features/05-rosettes-ply-directions.py index b49c921f52..497e0ccb92 100644 --- a/examples/modeling_features/05-rosettes-ply-directions.py +++ b/examples/modeling_features/05-rosettes-ply-directions.py @@ -23,8 +23,8 @@ """ .. _rosette_example: -Rosette example -=============== +Rosette +======= This example illustrates how you can use rosettes to define the reference directions of a ply. It only shows the PyACP part of the setup. For a complete composite analysis, diff --git a/examples/modeling_features/050-composite_cae_h5.py b/examples/modeling_features/050-composite_cae_h5.py new file mode 100644 index 0000000000..525c9ef21f --- /dev/null +++ b/examples/modeling_features/050-composite_cae_h5.py @@ -0,0 +1,238 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _composite_cae_h5_example: + +HDF5 Composite CAE +================== + +The HDF5 Composite CAE interface of PyACP is demonstrated in +this example. It shows how to write (export) and read (import) +layup data to and from a HDF5 Composite CAE file, respectively. +The HDF5 Composite CAE format is a vendor independent format +to exchange composite layup information between CAE tools. + +This examples demonstrates how to: +- Load and manipulate a model +- Export data to a HDF5 Composite CAE file +- Import and map layup from HDF5 Composite CAE onto a different model (mesh) +- Export data with ply offsets (3D plies) +- Import a layup as :class:`.ImportedModelingPly` +- Import HDF5 Composite CAE with 3D plies and map the layup onto an :class:`.ImportedSolidModel` + +""" + +# %% +# Import the standard library and third-party dependencies. +import pathlib +import tempfile + +import pyvista + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import ( + HDF5CompositeCAEProjectionMode, + LinkedSelectionRule, + OffsetType, + launch_acp, +) +from ansys.acp.core.extras import ( + FLAT_PLATE_SHELL_CAMERA, + FLAT_PLATE_SOLID_CAMERA, + ExampleKeys, + get_example_file, +) + +# sphinx_gallery_thumbnail_number = 2 + + +# %% +# Start ACP and load the model +# ---------------------------- +# %% +# Get the example file from the server. +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +acph5_input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_ACPH5, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Load the model from an acph5 file +model = acp.import_model(acph5_input_file) + +# %% +# Crop some plies in order to generate a variable laminate +pr_x = model.create_parallel_selection_rule( + name="x axis", + direction=(1, 0, 0), + lower_limit=0.0025, + upper_limit=0.0075, +) +pr_z = model.create_parallel_selection_rule( + name="z axis", + direction=(0, 0, 1), + lower_limit=0.0015, + upper_limit=0.0085, +) +boolean_rule = model.create_boolean_selection_rule( + name="boolean rule", + selection_rules=[LinkedSelectionRule(pr_x), LinkedSelectionRule(pr_z)], +) + +for ply_name in ["ply_1_45_UD", "ply_2_-45_UD", "ply_3_45_UD", "ply_4_-45_UD"]: + ply = model.modeling_groups["modeling_group"].modeling_plies[ply_name] + ply.selection_rules = [LinkedSelectionRule(boolean_rule)] + +model.update() + +# %% +# Plot the thickness distribution +thickness = model.elemental_data.thickness +assert thickness is not None +thickness.get_pyvista_mesh(mesh=model.mesh).plot(show_edges=True) + +# %% +# Write HDF5 Composite CAE file +# ----------------------------- +# +# Export the entire layup to a HDF5 Composite CAE file. +h5_output_file = WORKING_DIR / "hdf5_composite_cae.h5" +model.export_hdf5_composite_cae( + path=h5_output_file, +) + +# %% +# Load HDF5 Composite CAE file into a different model +# --------------------------------------------------- +# +# A new acp model is created by importing a refined mesh of the same geometry. +# Both meshes (initial mesh in blue, refined one in red) are shown below. +dat_input_file_refined = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_REFINED_DAT, WORKING_DIR) +refined_model = acp.import_model(path=dat_input_file_refined, format="ansys:dat") + +plotter = pyvista.Plotter() +plotter.add_mesh( + model.shell_mesh.to_pyvista(), + color="blue", + edge_color="blue", + show_edges=True, + style="wireframe", + line_width=4, +) +plotter.add_mesh( + refined_model.shell_mesh.to_pyvista(), + color="red", + edge_color="red", + show_edges=True, + style="wireframe", + line_width=2, +) +plotter.camera_position = FLAT_PLATE_SHELL_CAMERA +plotter.show() + +# %% +# Import the HDF5 Composite CAE file which is then automatically mapped +# onto the refined mesh. In this example, the default settings +# (tolerances, etc.) are used. +refined_model.import_hdf5_composite_cae( + path=h5_output_file, +) +refined_model.update() + +# %% +# Plot the thickness distribution on the refined model +thickness = refined_model.elemental_data.thickness +assert thickness is not None +thickness.get_pyvista_mesh(mesh=refined_model.mesh).plot(show_edges=True) + +# %% +# 3D plies with ply-offsets +# ------------------------- +# +# The HDF5 Composite CAE interface also allows to export the 3D plies +# (plies with offsets) which can then be used to create +# imported modeling plies. The initial model is used to +# write a new HDF5 with ``layup_representation_3d`` enabled. +h5_output_file_3D = WORKING_DIR / "hdf5_composite_cae_3D.h5" +model.export_hdf5_composite_cae( + path=h5_output_file_3D, + layup_representation_3d=True, + offset_type=OffsetType.BOTTOM_OFFSET, +) + +# %% +# A new acp model is created to properly separate the different workflows. +refined_model_3D = acp.import_model(path=dat_input_file_refined, format="ansys:dat") +refined_model_3D.import_hdf5_composite_cae( + path=h5_output_file_3D, projection_mode=HDF5CompositeCAEProjectionMode.SOLID +) + +# %% +# An imported solid model is required for the 3D workflow (with imported modeling plies). +# Details about :class:`.ImportedSolidModel` and :class:`.ImportedModelingPly` can be found +# in the examples :ref:`imported_solid_model_example` and :ref:`imported_plies_example`. +local_solid_mesh_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_SOLID_MESH_CDB, WORKING_DIR) +remote_solid_mesh_file = acp.upload_file(local_solid_mesh_file) +imported_solid_model = refined_model_3D.create_imported_solid_model( + name="Imported Solid Model", + external_path=remote_solid_mesh_file, + format="ansys:cdb", +) + +# %% +# The :class:`.LayupMappingObject` is used to configure the mapping of the imported plies +# onto the imported solid model. +imported_solid_model.create_layup_mapping_object( + name="Map imported plies", + use_imported_plies=True, # enable imported plies + select_all_plies=True, # select all plies + scale_ply_thicknesses=True, + entire_solid_mesh=True, + delete_lost_elements=True, # elements without plies are deleted +) +refined_model_3D.update() + +# %% +# The mapped top layer of the imported laminate is shown below. +# Note that the solid elements which do not intersect with the +# layup are deleted in this example. +imported_analysis_ply = ( + refined_model_3D.imported_modeling_groups["modeling_group"] + .imported_modeling_plies["ply_5_0_UD"] + .imported_production_plies["ImportedProductionPly.6"] + .imported_analysis_plies["P1L1__ply_5_0_UD"] +) +plotter = pyvista.Plotter() +plotter.add_mesh(imported_analysis_ply.solid_mesh.to_pyvista(), show_edges=True) +plotter.add_mesh(refined_model_3D.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +plotter.camera_position = FLAT_PLATE_SOLID_CAMERA +plotter.show() + +# %% +# Note that the visualization of imported plies and imported solid model +# is limited. As an alternative, you can save the model and review it +# in ACP standalone. diff --git a/examples/modeling_features/06-ply-direction-lookup-table.py b/examples/modeling_features/06-ply-direction-lookup-table.py index 38813bd603..72e5d910f3 100644 --- a/examples/modeling_features/06-ply-direction-lookup-table.py +++ b/examples/modeling_features/06-ply-direction-lookup-table.py @@ -23,8 +23,8 @@ """ .. _direction_definition_example: -Direction definition example -============================ +Direction definition +==================== This example shows how to define directions from lookup tables. They can be either reference directions for oriented selection sets or draping angles for modeling plies. diff --git a/examples/modeling_features/07-imported-plies.py b/examples/modeling_features/07-imported-plies.py new file mode 100644 index 0000000000..fe9cbf9d8d --- /dev/null +++ b/examples/modeling_features/07-imported-plies.py @@ -0,0 +1,245 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _imported_plies_example: + +Imported ply +============ + +The definition and use of imported plies is demonstrated in this example. +In a nutshell, the difference between :class:`.ImportedModelingPly` and +:class:`.ModelingPly` is that the surface mesh of an :class:`.ImportedModelingPly` +is defined by an external source, such as a CAD surface, where a :class:`.ModelingPly` +is always defined on the initial loaded shell mesh. +Therefore, an imported ply can only be used in combination with an +:class:`.ImportedSolidModel`. + +This examples shows hot to: + +- Load an initial mesh +- Add a :class:`.Material` and :class:`.Fabric` +- Import geometries which will be used to define the surface of the :class:`.ImportedModelingPly` +- Add two imported modeling plies +- Create an :class:`.ImportedSolidModel` +- Map the imported plies to the solid model +- Visualized the mapped plies. +""" + +# %% +# Import modules +# -------------- +# +# Import the standard library and third-party dependencies. +import os +import pathlib +import tempfile + +import pyvista + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import CADGeometry, ImportedPlyOffsetType, PlyType, VirtualGeometry, launch_acp +from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.material_property_sets import ConstantDensity, ConstantEngineeringConstants + +# sphinx_gallery_thumbnail_number = 3 + + +CAMERA_POSITION = [(0.0436, 0.0102, 0.0193), (0.0111, 0.0035, 0.0046), (-0.1685, 0.9827, -0.0773)] + +# %% +# Start ACP and load the model +# ---------------------------- + +# %% +# Get the example file from the server. +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_DAT, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Create a model by loading a shell mesh +model = acp.import_model(path=input_file, format="ansys:dat") + +# %% +# Add a material and a fabric with 1mm thickness. +# The fabric is used for the imported modeling ply. +engineering_constants_ud = ConstantEngineeringConstants.from_orthotropic_constants( + E1=5e10, E2=1e10, E3=1e10, nu12=0.28, nu13=0.28, nu23=0.3, G12=5e9, G23=4e9, G31=4e9 +) +density_ud = ConstantDensity(rho=2700) + +ud_material = model.create_material( + name="E-Glass UD", + ply_type=PlyType.REGULAR, + engineering_constants=engineering_constants_ud, + density=density_ud, +) + +engineering_resin = ConstantEngineeringConstants.from_isotropic_constants(E=5e9, nu=0.3) +density_resin = ConstantDensity(rho=1200) + +void_material = model.create_material( + name="Void material", + ply_type=PlyType.ISOTROPIC, + engineering_constants=engineering_resin, + density=density_resin, +) +filler_material = model.create_material( + name="Filler material", + ply_type=PlyType.ISOTROPIC, + engineering_constants=engineering_resin, + density=density_resin, +) + +fabric = model.create_fabric(name="E-Glass Fabric", material=ud_material, thickness=0.001) + + +# %% +# Import CAD geometries +# --------------------- +# +# Import two cad surfaces to define the surface of the imported modeling plies. +def create_virtual_geometry_from_file( + example_key: ExampleKeys, +) -> tuple[CADGeometry, VirtualGeometry]: + """Create a CAD geometry and virtual geometry.""" + geometry_file = get_example_file(example_key, WORKING_DIR) + geometry_obj = model.create_cad_geometry() + geometry_obj.refresh(geometry_file) # upload and load the geometry file + model.update() + virtual_geometry = model.create_virtual_geometry( + name=os.path.basename(geometry_file), cad_components=geometry_obj.root_shapes.values() + ) + return geometry_obj, virtual_geometry + + +triangle_surf_cad, triangle_surf_vcad = create_virtual_geometry_from_file( + ExampleKeys.RULE_GEOMETRY_TRIANGLE +) +top_surf_cad, top_surf_vcad = create_virtual_geometry_from_file(ExampleKeys.SNAP_TO_GEOMETRY) + +# %% +# Definition of Imported Plies +# ---------------------------- +imported_ply_group = model.create_imported_modeling_group(name="Imported Ply Group") +imported_ply_triangle = imported_ply_group.create_imported_modeling_ply( + name="Triangle Ply", + offset_type=ImportedPlyOffsetType.BOTTOM_OFFSET, + ply_material=fabric, + mesh_geometry=triangle_surf_vcad, + ply_angle=0, + rosettes=[model.rosettes["12"]], +) + +imported_ply_top = imported_ply_group.create_imported_modeling_ply( + name="Triangle Ply", + offset_type=ImportedPlyOffsetType.MIDDLE_OFFSET, + ply_material=fabric, + mesh_geometry=top_surf_vcad, + ply_angle=45, + rosettes=[model.rosettes["12"]], +) +model.update() + + +# %% +# Imported plies cannot be visualized directly yet but the cad geometries are +# shown here instead. +# To visualize the imported plies, you can save the model and load it in ACP +# standalone. +def plotter_with_all_geometries(cad_geometries): + colors = ["green", "yellow", "blue", "red"] + plotter = pyvista.Plotter() + for index, cad in enumerate(cad_geometries): + geom_mesh = cad.visualization_mesh.to_pyvista() + plotter.add_mesh(geom_mesh, color=colors[index], opacity=0.1) + edges = geom_mesh.extract_feature_edges() + plotter.add_mesh(edges, color="white", line_width=4) + plotter.add_mesh(edges, color="black", line_width=2) + plotter.camera_position = CAMERA_POSITION + return plotter + + +plotter = plotter_with_all_geometries([triangle_surf_cad, top_surf_cad]) +plotter.show() + +# %% +# Map Imported Plies onto a solid mesh +# ------------------------------------ +# +# An external solid mesh is loaded now to map the imported plies +# onto the solid model. The next figure shows the imported solid mesh +# and the imported plies. +local_solid_mesh_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_SOLID_MESH_CDB, WORKING_DIR) +remote_solid_mesh_file = acp.upload_file(local_solid_mesh_file) +imported_solid_model = model.create_imported_solid_model( + name="Imported Solid Model", + external_path=remote_solid_mesh_file, + format="ansys:cdb", +) +imported_solid_model.import_initial_mesh() +plotter = plotter_with_all_geometries([triangle_surf_cad, top_surf_cad]) +plotter.add_mesh(imported_solid_model.solid_mesh.to_pyvista(), show_edges=True, opacity=0.5) +plotter.show() + +# %% +# Add a mapping object to link the imported plies with the solid model. +# In this example, all imported plies are mapped in one go. +# The remaining elemental volume and elements which do not intersect +# with the imported plies are filled with a void and filler material, +# respectively. +imported_solid_model.create_layup_mapping_object( + name="Map imported plies", + use_imported_plies=True, # enable imported plies + select_all_plies=True, # select all plies + entire_solid_mesh=True, + scale_ply_thicknesses=False, + void_material=void_material, + delete_lost_elements=False, + filler_material=filler_material, + rosettes=[model.rosettes["12"]], +) +model.update() + +# %% +# Show the imported ply geometries and mapped plies on the solid model. +# Note that the analysis plies are not yet directly accessible via +# the API of the imported solid model. Also, elemental data such as +# thicknesses are not yet implemented for imported plies. +plotter = plotter_with_all_geometries([triangle_surf_cad, top_surf_cad]) +for imported_ply in [imported_ply_triangle, imported_ply_top]: + for pp in imported_ply.imported_production_plies.values(): + for ap in pp.imported_analysis_plies.values(): + plotter.add_mesh(ap.solid_mesh.to_pyvista(), show_edges=True, opacity=1) +plotter.add_mesh(mesh=imported_solid_model.solid_mesh.to_pyvista(), show_edges=False, opacity=0.2) +plotter.show() + +# %% +# The imported solid model can be passed to Mechanical or MAPDL to run an analysis +# as shown in the examples :ref:`pymechanical_solid_example` and +# :ref:`pymapdl_workflow_example`. diff --git a/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py b/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py index 5a5b42b03e..7c41744fff 100644 --- a/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py +++ b/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py @@ -32,6 +32,7 @@ grpc_link_property_read_only, mark_grpc_properties, ) +from ._mesh_data import solid_mesh_property from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -76,3 +77,5 @@ def _create_stub(self) -> imported_analysis_ply_pb2_grpc.ObjectServiceStub: active_in_post_mode: ReadOnlyProperty[bool] = grpc_data_property_read_only( "properties.active_in_post_mode" ) + + solid_mesh = solid_mesh_property diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index 55f534b985..eb56434ede 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -57,6 +57,7 @@ grpc_link_property, mark_grpc_properties, ) +from ._mesh_data import solid_mesh_property from ._solid_model_export import SolidModelExportMixin from .base import ( CreatableTreeObject, @@ -380,3 +381,5 @@ def import_initial_mesh(self) -> None: self._get_stub().ImportInitialMesh( # type: ignore imported_solid_model_pb2.ImportInitialMeshRequest(resource_path=self._resource_path) ) + + solid_mesh = solid_mesh_property diff --git a/src/ansys/acp/core/extras/__init__.py b/src/ansys/acp/core/extras/__init__.py index 02b7305b3e..8d20f00f4f 100644 --- a/src/ansys/acp/core/extras/__init__.py +++ b/src/ansys/acp/core/extras/__init__.py @@ -21,9 +21,16 @@ # SOFTWARE. """Extras of the Ansys Composites PrepPost module.""" -from ansys.acp.core.extras.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core.extras.example_helpers import ( + FLAT_PLATE_SHELL_CAMERA, + FLAT_PLATE_SOLID_CAMERA, + ExampleKeys, + get_example_file, +) __all__ = [ "ExampleKeys", "get_example_file", + "FLAT_PLATE_SHELL_CAMERA", + "FLAT_PLATE_SOLID_CAMERA", ] diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index cc59e277b3..141b85a723 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -46,6 +46,19 @@ # _EXAMPLE_REPO = "D:\\ANSYSDev\\pyansys-example-data\\pyacp\\" +# Order of inputs: position, rotation point, orientation +FLAT_PLATE_SHELL_CAMERA = [ + (-0.0053, 0.0168, 0.0220), + (0.0022, 0.0041, 0.0104), + (0.3510, 0.7368, -0.5779), +] +FLAT_PLATE_SOLID_CAMERA = [ + (0.0251, 0.0144, 0.0256), + (0.0086, 0.0041, 0.0089), + (-0.2895, 0.9160, -0.2776), +] + + @dataclasses.dataclass class _ExampleLocation: directory: str @@ -56,7 +69,9 @@ class ExampleKeys(Enum): """Keys for the example files.""" BASIC_FLAT_PLATE_DAT = auto() + BASIC_FLAT_PLATE_REFINED_DAT = auto() BASIC_FLAT_PLATE_ACPH5 = auto() + BASIC_FLAT_PLATE_SOLID_MESH_CDB = auto() RACE_CAR_NOSE_ACPH5 = auto() RACE_CAR_NOSE_STEP = auto() CUT_OFF_GEOMETRY = auto() @@ -77,9 +92,15 @@ class ExampleKeys(Enum): ExampleKeys.BASIC_FLAT_PLATE_DAT: _ExampleLocation( directory="basic_flat_plate_example", filename="flat_plate_input.dat" ), + ExampleKeys.BASIC_FLAT_PLATE_REFINED_DAT: _ExampleLocation( + directory="basic_flat_plate_example", filename="flat_plate_input_refined.dat" + ), ExampleKeys.BASIC_FLAT_PLATE_ACPH5: _ExampleLocation( directory="basic_flat_plate_example", filename="flat_plate.acph5" ), + ExampleKeys.BASIC_FLAT_PLATE_SOLID_MESH_CDB: _ExampleLocation( + directory="basic_flat_plate_example", filename="solid_mesh.cdb" + ), ExampleKeys.RACE_CAR_NOSE_ACPH5: _ExampleLocation( directory="race_car_nose", filename="race_car_nose.acph5" ), diff --git a/tests/unittests/test_imported_solid_model.py b/tests/unittests/test_imported_solid_model.py index 16e7229880..0080a57b9e 100644 --- a/tests/unittests/test_imported_solid_model.py +++ b/tests/unittests/test_imported_solid_model.py @@ -260,6 +260,8 @@ def test_import_initial_mesh(acp_instance, parent_object): format=pyacp.SolidModelImportFormat.ANSYS_H5, ) imported_solid_model.import_initial_mesh() + assert imported_solid_model.solid_mesh is not None + assert imported_solid_model.solid_mesh.element_labels == (3,) # refresh from external source with the same format imported_solid_model.refresh(out_path_h5) @@ -268,3 +270,5 @@ def test_import_initial_mesh(acp_instance, parent_object): # refresh from external source where the format is different imported_solid_model.refresh(out_path_cdb, format=pyacp.SolidModelImportFormat.ANSYS_CDB) imported_solid_model.import_initial_mesh() + assert imported_solid_model.solid_mesh is not None + assert imported_solid_model.solid_mesh.element_labels == (3,) From 49cf253533d29af0c2110d2b939301071ae89116 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 28 Nov 2024 19:49:59 +0100 Subject: [PATCH 86/96] Add 'analysis_plies' property to solid model (#715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a read-only 'analysis_plies' collection to the solid model. This collection contains only the AnalysisPly objects, the InterfaceLayer objects are not included. Partially addresses #711. Co-authored-by: René Roos <105842014+roosre@users.noreply.github.com> --- examples/modeling_features/020-solid_model.py | 7 +------ src/ansys/acp/core/_tree_objects/solid_model.py | 6 ++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/modeling_features/020-solid_model.py b/examples/modeling_features/020-solid_model.py index 44f42335e7..244a12a104 100644 --- a/examples/modeling_features/020-solid_model.py +++ b/examples/modeling_features/020-solid_model.py @@ -193,12 +193,7 @@ def create_virtual_geometry_from_file( # %% # Get the analysis ply of interest -ap = ( - model.modeling_groups["modeling_group"] - .modeling_plies["ply"] - .production_plies["ProductionPly.2"] - .analysis_plies["P2L1__ply"] -) +ap = solid_model.analysis_plies["P2L1__ply"] # %% # Plot fiber directions diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index 3e279efe4f..7b106ec93a 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -27,6 +27,7 @@ from typing import Any from ansys.api.acp.v0 import ( + analysis_ply_pb2_grpc, cut_off_geometry_pb2_grpc, extrusion_guide_pb2_grpc, snap_to_geometry_pb2_grpc, @@ -59,6 +60,7 @@ mark_grpc_properties, ) from ._solid_model_export import SolidModelExportMixin +from .analysis_ply import AnalysisPly from .base import ( CreatableTreeObject, IdTreeObject, @@ -525,5 +527,9 @@ def _create_stub(self) -> solid_model_pb2_grpc.ObjectServiceStub: CutOffGeometry, cut_off_geometry_pb2_grpc.ObjectServiceStub ) + analysis_plies = get_read_only_collection_property( + AnalysisPly, analysis_ply_pb2_grpc.ObjectServiceStub + ) + elemental_data = elemental_data_property(SolidModelElementalData) nodal_data = nodal_data_property(SolidModelNodalData) From ca187a54b8d52943fea3d8ee8ffe0be8691175f0 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 28 Nov 2024 20:11:31 +0100 Subject: [PATCH 87/96] Make cut off and drop off names consistent (#718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the "cut off" and "drop off" related features to make them consistent: - `Cutoff` -> `CutOff` - `cutoff` -> `cut_off` - `Dropoff` -> `DropOff` - `dropoff` -> `drop_off` Partly addresses #637. Co-authored-by: René Roos <105842014+roosre@users.noreply.github.com> --- doc/source/api/enum_types.rst | 8 +- doc/source/api/mesh_data.rst | 4 +- doc/source/api/tree_objects.rst | 2 +- doc/source/user_guide/howto/print_model.rst | 2 +- examples/modeling_features/020-solid_model.py | 6 +- .../03-advanced-selection-rules.py | 26 +++---- src/ansys/acp/core/__init__.py | 20 ++--- src/ansys/acp/core/_tree_objects/__init__.py | 30 +++---- ...tion_rule.py => cut_off_selection_rule.py} | 78 +++++++++---------- src/ansys/acp/core/_tree_objects/enums.py | 36 ++++----- src/ansys/acp/core/_tree_objects/fabric.py | 12 +-- .../_tree_objects/linked_selection_rule.py | 12 +-- src/ansys/acp/core/_tree_objects/model.py | 12 +-- .../acp/core/_tree_objects/solid_model.py | 32 ++++---- src/ansys/acp/core/_tree_objects/stackup.py | 12 +-- src/ansys/acp/core/mesh_data.py | 8 +- ...rule.py => test_cut_off_selection_rule.py} | 32 ++++---- tests/unittests/test_fabric.py | 22 +++--- tests/unittests/test_modeling_ply.py | 10 +-- tests/unittests/test_solid_model.py | 78 +++++++++---------- tests/unittests/test_stackup.py | 12 +-- tests/unittests/test_tree_printer.py | 6 +- type_checks/add_methods.py | 4 +- 23 files changed, 232 insertions(+), 232 deletions(-) rename src/ansys/acp/core/_tree_objects/{cutoff_selection_rule.py => cut_off_selection_rule.py} (71%) rename tests/unittests/{test_cutoff_selection_rule.py => test_cut_off_selection_rule.py} (70%) diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index 45f9808735..f725fd0875 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -11,12 +11,12 @@ Enumeration data types BaseElementMaterialHandling BooleanOperationType CutOffGeometryOrientationType - CutoffMaterialHandling - CutoffRuleType + CutOffMaterialHandling + CutOffRuleType DimensionType DrapingMaterialModel DrapingType - DropoffMaterialHandling + DropOffMaterialHandling DropOffType EdgeSetType ElementalDataType @@ -42,7 +42,7 @@ Enumeration data types SolidModelOffsetDirectionType OffsetType SnapToGeometryOrientationType - PlyCutoffType + PlyCutOffType PlyGeometryExportFormat PlyType ReinforcingBehavior diff --git a/doc/source/api/mesh_data.rst b/doc/source/api/mesh_data.rst index 1423ace5d6..3246a99aa5 100644 --- a/doc/source/api/mesh_data.rst +++ b/doc/source/api/mesh_data.rst @@ -10,8 +10,8 @@ Mesh data objects AnalysisPlyNodalData BooleanSelectionRuleElementalData BooleanSelectionRuleNodalData - CutoffSelectionRuleElementalData - CutoffSelectionRuleNodalData + CutOffSelectionRuleElementalData + CutOffSelectionRuleNodalData CylindricalSelectionRuleElementalData CylindricalSelectionRuleNodalData ElementSetElementalData diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 170610f494..9905adf6da 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -12,7 +12,7 @@ ACP objects CADComponent CADGeometry CutOffGeometry - CutoffSelectionRule + CutOffSelectionRule CylindricalSelectionRule EdgeSet ElementSet diff --git a/doc/source/user_guide/howto/print_model.rst b/doc/source/user_guide/howto/print_model.rst index 8eaa709f90..3930f7f901 100644 --- a/doc/source/user_guide/howto/print_model.rst +++ b/doc/source/user_guide/howto/print_model.rst @@ -90,7 +90,7 @@ The ``hide_empty`` label can be set to ``False`` to also show empty groups: Cylindrical Selection Rules Spherical Selection Rules Tube Selection Rules - Cutoff Selection Rules + Cut Off Selection Rules Geometrical Selection Rules Variable Offset Selection Rules Boolean Selection Rules diff --git a/examples/modeling_features/020-solid_model.py b/examples/modeling_features/020-solid_model.py index 244a12a104..ca2e92b4ae 100644 --- a/examples/modeling_features/020-solid_model.py +++ b/examples/modeling_features/020-solid_model.py @@ -171,17 +171,17 @@ def create_virtual_geometry_from_file( # --------------- # # The :class:`.CutOffGeometry` is used to crop elements from the solid model. -cutoff_cad_geom, cutoff_virtual_geom = create_virtual_geometry_from_file( +cut_off_cad_geom, cut_off_virtual_geom = create_virtual_geometry_from_file( ExampleKeys.CUT_OFF_GEOMETRY_SOLID_MODEL ) solid_model.create_cut_off_geometry( name="Cut-off Geometry", - cad_geometry=cutoff_virtual_geom, + cad_geometry=cut_off_virtual_geom, orientation_type=CutOffGeometryOrientationType.UP, ) model.update() -plot_model_with_geometry(cutoff_cad_geom) +plot_model_with_geometry(cut_off_cad_geom) # %% diff --git a/examples/modeling_features/03-advanced-selection-rules.py b/examples/modeling_features/03-advanced-selection-rules.py index 2386f796fe..38d13638c8 100644 --- a/examples/modeling_features/03-advanced-selection-rules.py +++ b/examples/modeling_features/03-advanced-selection-rules.py @@ -179,14 +179,14 @@ plotter.show() # %% -# Create a cutoff selection rule -# ------------------------------ +# Create a cut-off selection rule +# ------------------------------- # %% -# Add the cutoff CAD geometry to the model. -cutoff_plane_path = get_example_file(ExampleKeys.CUT_OFF_GEOMETRY, WORKING_DIR) +# Add the cut off CAD geometry to the model. +cut_off_plane_path = get_example_file(ExampleKeys.CUT_OFF_GEOMETRY, WORKING_DIR) cut_off_plane = model.create_cad_geometry() -cut_off_plane.refresh(cutoff_plane_path) +cut_off_plane.refresh(cut_off_plane_path) # Note: It is important to update the model here, because the root_shapes of the # cad_geometry are not available until the model is updated. @@ -194,22 +194,22 @@ # %% # Create a virtual geometry from the CAD geometry. -cutoff_virtual_geometry = model.create_virtual_geometry( - name="cutoff_virtual_geometry", cad_components=cut_off_plane.root_shapes.values() +cut_off_virtual_geometry = model.create_virtual_geometry( + name="cut_off_virtual_geometry", cad_components=cut_off_plane.root_shapes.values() ) # %% -# Create the cutoff selection rule. -cutoff_selection_rule = model.create_cutoff_selection_rule( - name="cutoff_rule", - cutoff_geometry=cutoff_virtual_geometry, +# Create the cut_off selection rule. +cut_off_selection_rule = model.create_cut_off_selection_rule( + name="cut_off_rule", + cut_off_geometry=cut_off_virtual_geometry, ) # %% -# Assign the cutoff selection rule to the ply. Plot the ply extent with +# Assign the cut_off selection rule to the ply. Plot the ply extent with # the outline of the geometry. -modeling_ply.selection_rules = [LinkedSelectionRule(cutoff_selection_rule)] +modeling_ply.selection_rules = [LinkedSelectionRule(cut_off_selection_rule)] model.update() assert model.elemental_data.thickness is not None diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 63186aacf7..5d793e7944 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -57,14 +57,14 @@ CoordinateTransformation, CutOffGeometry, CutOffGeometryOrientationType, - CutoffMaterialHandling, - CutoffRuleType, - CutoffSelectionRule, + CutOffMaterialHandling, + CutOffRuleType, + CutOffSelectionRule, CylindricalSelectionRule, DimensionType, DrapingMaterialModel, DrapingType, - DropoffMaterialHandling, + DropOffMaterialHandling, DropOffSettings, DropOffType, EdgeSet, @@ -115,7 +115,7 @@ OffsetType, OrientedSelectionSet, ParallelSelectionRule, - PlyCutoffType, + PlyCutOffType, PlyGeometryExportFormat, PlyType, PrimaryPly, @@ -175,16 +175,16 @@ "CoordinateTransformation", "CutOffGeometry", "CutOffGeometryOrientationType", - "CutoffMaterialHandling", - "CutoffRuleType", - "CutoffSelectionRule", + "CutOffMaterialHandling", + "CutOffRuleType", + "CutOffSelectionRule", "CylindricalSelectionRule", "DimensionType", "DirectLaunchConfig", "DockerComposeLaunchConfig", "DrapingMaterialModel", "DrapingType", - "DropoffMaterialHandling", + "DropOffMaterialHandling", "DropOffSettings", "DropOffType", "EdgeSet", @@ -245,7 +245,7 @@ "OffsetType", "OrientedSelectionSet", "ParallelSelectionRule", - "PlyCutoffType", + "PlyCutOffType", "PlyGeometryExportFormat", "SolidModelOffsetDirectionType", "PlyType", diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 3968b7e434..3559a48a2f 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -32,10 +32,10 @@ from .cad_component import CADComponent from .cad_geometry import CADGeometry, TriangleMesh from .cut_off_geometry import CutOffGeometry -from .cutoff_selection_rule import ( - CutoffSelectionRule, - CutoffSelectionRuleElementalData, - CutoffSelectionRuleNodalData, +from .cut_off_selection_rule import ( + CutOffSelectionRule, + CutOffSelectionRuleElementalData, + CutOffSelectionRuleNodalData, ) from .cylindrical_selection_rule import ( CylindricalSelectionRule, @@ -49,12 +49,12 @@ BaseElementMaterialHandling, BooleanOperationType, CutOffGeometryOrientationType, - CutoffMaterialHandling, - CutoffRuleType, + CutOffMaterialHandling, + CutOffRuleType, DimensionType, DrapingMaterialModel, DrapingType, - DropoffMaterialHandling, + DropOffMaterialHandling, DropOffType, EdgeSetType, ElementalDataType, @@ -72,7 +72,7 @@ MeshImportType, NodalDataType, OffsetType, - PlyCutoffType, + PlyCutOffType, PlyGeometryExportFormat, PlyType, ReinforcingBehavior, @@ -196,18 +196,18 @@ "CoordinateTransformation", "CutOffGeometry", "CutOffGeometryOrientationType", - "CutoffMaterialHandling", - "CutoffRuleType", - "CutoffSelectionRule", - "CutoffSelectionRuleElementalData", - "CutoffSelectionRuleNodalData", + "CutOffMaterialHandling", + "CutOffRuleType", + "CutOffSelectionRule", + "CutOffSelectionRuleElementalData", + "CutOffSelectionRuleNodalData", "CylindricalSelectionRule", "CylindricalSelectionRuleElementalData", "CylindricalSelectionRuleNodalData", "DimensionType", "DrapingMaterialModel", "DrapingType", - "DropoffMaterialHandling", + "DropOffMaterialHandling", "DropOffSettings", "DropOffType", "EdgeSet", @@ -276,7 +276,7 @@ "ParallelSelectionRule", "ParallelSelectionRuleElementalData", "ParallelSelectionRuleNodalData", - "PlyCutoffType", + "PlyCutOffType", "PlyGeometryExportFormat", "PlyType", "PrimaryPly", diff --git a/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py b/src/ansys/acp/core/_tree_objects/cut_off_selection_rule.py similarity index 71% rename from src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py rename to src/ansys/acp/core/_tree_objects/cut_off_selection_rule.py index a297f4ec00..402abcfaa1 100644 --- a/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cut_off_selection_rule.py @@ -45,12 +45,12 @@ from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .enums import ( - CutoffRuleType, - PlyCutoffType, - cutoff_rule_type_from_pb, - cutoff_rule_type_to_pb, - ply_cutoff_type_from_pb, - ply_cutoff_type_to_pb, + CutOffRuleType, + PlyCutOffType, + cut_off_rule_type_from_pb, + cut_off_rule_type_to_pb, + ply_cut_off_type_from_pb, + ply_cut_off_type_to_pb, status_type_from_pb, ) from .object_registry import register @@ -63,50 +63,50 @@ __all__ = [ - "CutoffSelectionRule", - "CutoffSelectionRuleElementalData", - "CutoffSelectionRuleNodalData", + "CutOffSelectionRule", + "CutOffSelectionRuleElementalData", + "CutOffSelectionRuleNodalData", ] @dataclasses.dataclass -class CutoffSelectionRuleElementalData(ElementalData): - """Represents elemental data for a Cutoff Selection Rule.""" +class CutOffSelectionRuleElementalData(ElementalData): + """Represents elemental data for a Cut-Off Selection Rule.""" normal: VectorData | None = None @dataclasses.dataclass -class CutoffSelectionRuleNodalData(NodalData): - """Represents nodal data for a Cutoff Selection Rule.""" +class CutOffSelectionRuleNodalData(NodalData): + """Represents nodal data for a Cut-Off Selection Rule.""" @mark_grpc_properties @register -class CutoffSelectionRule(CreatableTreeObject, IdTreeObject): - """Instantiate a Cutoff Selection Rule. +class CutOffSelectionRule(CreatableTreeObject, IdTreeObject): + """Instantiate a Cut-Off Selection Rule. Parameters ---------- name : - Name of the Cutoff Selection Rule. - cutoff_rule_type : + Name of the Cut-Off Selection Rule. + cut_off_rule_type : Determines if the cut-off is defined by a geometry or by a tapering edge. - cutoff_geometry : + cut_off_geometry : Geometry used to define the cut-off. Only applies if - ``cutoff_rule_type`` is GEOMETRY. + ``cut_off_rule_type`` is GEOMETRY. taper_edge_set : Edge used to define the cut-off. Only applies if - ``cutoff_rule_type`` is :attr:`.CutoffRuleType.TAPER`. + ``cut_off_rule_type`` is :attr:`.CutOffRuleType.TAPER`. offset : Moves the cutting plane along the out-of-plane direction. Always measured from the reference surface. angle : Defines the angle between the cutting plane and the reference surface. - ply_cutoff_type : + ply_cut_off_type : Either the complete production ply is cut-off - (:attr:`PlyCutoffType.PRODUCTION_PLY_CUTOFF`) or individual analysis plies - (:attr:`PlyCutoffType.ANALYSIS_PLY_CUTOFF`). + (:attr:`PlyCutOffType.PRODUCTION_PLY_CUTOFF`) or individual analysis plies + (:attr:`PlyCutOffType.ANALYSIS_PLY_CUTOFF`). ply_tapering : Whether the tapering of analysis plies is enabled. """ @@ -121,22 +121,22 @@ class CutoffSelectionRule(CreatableTreeObject, IdTreeObject): def __init__( self, *, - name: str = "CutoffSelectionrule", - cutoff_rule_type: CutoffRuleType = CutoffRuleType.GEOMETRY, - cutoff_geometry: VirtualGeometry | None = None, + name: str = "CutOffSelectionrule", + cut_off_rule_type: CutOffRuleType = CutOffRuleType.GEOMETRY, + cut_off_geometry: VirtualGeometry | None = None, taper_edge_set: EdgeSet | None = None, offset: float = 0.0, angle: float = 0.0, - ply_cutoff_type: PlyCutoffType = PlyCutoffType.PRODUCTION_PLY_CUTOFF, + ply_cut_off_type: PlyCutOffType = PlyCutOffType.PRODUCTION_PLY_CUTOFF, ply_tapering: bool = False, ): super().__init__(name=name) - self.cutoff_rule_type = cutoff_rule_type - self.cutoff_geometry = cutoff_geometry + self.cut_off_rule_type = cut_off_rule_type + self.cut_off_geometry = cut_off_geometry self.taper_edge_set = taper_edge_set self.offset = offset self.angle = angle - self.ply_cutoff_type = ply_cutoff_type + self.ply_cut_off_type = ply_cut_off_type self.ply_tapering = ply_tapering def _create_stub(self) -> cutoff_selection_rule_pb2_grpc.ObjectServiceStub: @@ -144,26 +144,26 @@ def _create_stub(self) -> cutoff_selection_rule_pb2_grpc.ObjectServiceStub: status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb) - cutoff_rule_type = grpc_data_property( + cut_off_rule_type = grpc_data_property( "properties.cutoff_rule_type", - from_protobuf=cutoff_rule_type_from_pb, - to_protobuf=cutoff_rule_type_to_pb, + from_protobuf=cut_off_rule_type_from_pb, + to_protobuf=cut_off_rule_type_to_pb, ) - cutoff_geometry = grpc_link_property( + cut_off_geometry = grpc_link_property( "properties.cutoff_geometry", allowed_types=VirtualGeometry ) taper_edge_set = grpc_link_property("properties.taper_edge_set", allowed_types=EdgeSet) offset: ReadWriteProperty[float, float] = grpc_data_property("properties.offset") angle: ReadWriteProperty[float, float] = grpc_data_property("properties.angle") - ply_cutoff_type = grpc_data_property( + ply_cut_off_type = grpc_data_property( "properties.ply_cutoff_type", - from_protobuf=ply_cutoff_type_from_pb, - to_protobuf=ply_cutoff_type_to_pb, + from_protobuf=ply_cut_off_type_from_pb, + to_protobuf=ply_cut_off_type_to_pb, ) ply_tapering: ReadWriteProperty[bool, bool] = grpc_data_property("properties.ply_tapering") mesh = full_mesh_property shell_mesh = shell_mesh_property # selection rules don't have solid mesh data - elemental_data = elemental_data_property(CutoffSelectionRuleElementalData) - nodal_data = nodal_data_property(CutoffSelectionRuleNodalData) + elemental_data = elemental_data_property(CutOffSelectionRuleElementalData) + nodal_data = nodal_data_property(CutOffSelectionRuleNodalData) diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index d4715cdd91..dda255f9b1 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -51,12 +51,12 @@ "ArrowType", "BooleanOperationType", "CutOffGeometryOrientationType", - "CutoffMaterialHandling", - "CutoffRuleType", + "CutOffMaterialHandling", + "CutOffRuleType", "DimensionType", "DrapingMaterialModel", "DrapingType", - "DropoffMaterialHandling", + "DropOffMaterialHandling", "DropOffType", "EdgeSetType", "ElementalDataType", @@ -77,7 +77,7 @@ "NodalDataType", "SolidModelOffsetDirectionType", "OffsetType", - "PlyCutoffType", + "PlyCutOffType", "PlyGeometryExportFormat", "PlyType", "RosetteSelectionMethod", @@ -114,22 +114,22 @@ ) ( - CutoffMaterialHandling, + CutOffMaterialHandling, cut_off_material_type_to_pb, cut_off_material_type_from_pb, ) = wrap_to_string_enum( - "CutoffMaterialHandling", + "CutOffMaterialHandling", cut_off_material_pb2.MaterialHandlingType, module=__name__, doc="Options for how cut-off material is selected.", ) ( - DropoffMaterialHandling, + DropOffMaterialHandling, drop_off_material_type_to_pb, drop_off_material_type_from_pb, ) = wrap_to_string_enum( - "DropoffMaterialHandling", + "DropOffMaterialHandling", drop_off_material_pb2.MaterialHandlingType, module=__name__, doc="Options for how drop-off material is selected.", @@ -344,25 +344,25 @@ ) ( - CutoffRuleType, - cutoff_rule_type_to_pb, - cutoff_rule_type_from_pb, + CutOffRuleType, + cut_off_rule_type_to_pb, + cut_off_rule_type_from_pb, ) = wrap_to_string_enum( - "CutoffRuleType", + "CutOffRuleType", cutoff_selection_rule_pb2.CutoffRuleType, module=__name__, - doc="Options for how a cutoff rule is defined.", + doc="Options for how a cut off rule is defined.", ) ( - PlyCutoffType, - ply_cutoff_type_to_pb, - ply_cutoff_type_from_pb, + PlyCutOffType, + ply_cut_off_type_to_pb, + ply_cut_off_type_from_pb, ) = wrap_to_string_enum( - "PlyCutoffType", + "PlyCutOffType", cutoff_selection_rule_pb2.PlyCutoffType, module=__name__, - doc="Options for how ply cutoff is computed.", + doc="Options for how ply cut-off is computed.", ) ( diff --git a/src/ansys/acp/core/_tree_objects/fabric.py b/src/ansys/acp/core/_tree_objects/fabric.py index 4ff1fc2f0c..5fd8e69afa 100644 --- a/src/ansys/acp/core/_tree_objects/fabric.py +++ b/src/ansys/acp/core/_tree_objects/fabric.py @@ -35,9 +35,9 @@ ) from .base import CreatableTreeObject, IdTreeObject from .enums import ( - CutoffMaterialHandling, + CutOffMaterialHandling, DrapingMaterialModel, - DropoffMaterialHandling, + DropOffMaterialHandling, cut_off_material_type_from_pb, cut_off_material_type_to_pb, draping_material_type_from_pb, @@ -100,9 +100,9 @@ def __init__( thickness: float = 0.0, area_price: float = 0.0, ignore_for_postprocessing: bool = False, - drop_off_material_handling: DropoffMaterialHandling = "global", + drop_off_material_handling: DropOffMaterialHandling = "global", drop_off_material: Material | None = None, - cut_off_material_handling: CutoffMaterialHandling = "computed", + cut_off_material_handling: CutOffMaterialHandling = "computed", cut_off_material: Material | None = None, draping_material_model: DrapingMaterialModel = "woven", draping_ud_coefficient: float = 0.0, @@ -113,9 +113,9 @@ def __init__( self.thickness = thickness self.area_price = area_price self.ignore_for_postprocessing = ignore_for_postprocessing - self.drop_off_material_handling = DropoffMaterialHandling(drop_off_material_handling) + self.drop_off_material_handling = DropOffMaterialHandling(drop_off_material_handling) self.drop_off_material = drop_off_material - self.cut_off_material_handling = CutoffMaterialHandling(cut_off_material_handling) + self.cut_off_material_handling = CutOffMaterialHandling(cut_off_material_handling) self.cut_off_material = cut_off_material self.draping_material_model = DrapingMaterialModel(draping_material_model) self.draping_ud_coefficient = draping_ud_coefficient diff --git a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py index bd852527e7..146f87b685 100644 --- a/src/ansys/acp/core/_tree_objects/linked_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/linked_selection_rule.py @@ -34,7 +34,7 @@ from ._grpc_helpers.polymorphic_from_pb import tree_object_from_resource_path from ._grpc_helpers.property_helper import _exposed_grpc_property, mark_grpc_properties from .base import CreatableTreeObject -from .cutoff_selection_rule import CutoffSelectionRule +from .cut_off_selection_rule import CutOffSelectionRule from .cylindrical_selection_rule import CylindricalSelectionRule from .enums import ( BooleanOperationType, @@ -54,7 +54,7 @@ _LINKABLE_SELECTION_RULE_TYPES: TypeAlias = Union[ "BooleanSelectionRule", - CutoffSelectionRule, + CutOffSelectionRule, CylindricalSelectionRule, GeometricalSelectionRule, ParallelSelectionRule, @@ -101,7 +101,7 @@ class LinkedSelectionRule(GenericEdgePropertyType): :class:`.BooleanSelectionRule` \- \- ====================================== ================================== =================== - Note that :class:`.CutoffSelectionRule` and :class:`.BooleanSelectionRule` objects cannot be linked to + Note that :class:`.CutOffSelectionRule` and :class:`.BooleanSelectionRule` objects cannot be linked to a Boolean Selection Rule, only to a Modeling Ply.. """ @@ -147,11 +147,11 @@ def operation_type(self) -> BooleanOperationType: def operation_type(self, value: BooleanOperationType) -> None: # The backend converts the operation automatically; this is confusing # in the scripting context where the associated warning may not be visible. - if isinstance(self._selection_rule, CutoffSelectionRule): + if isinstance(self._selection_rule, CutOffSelectionRule): if value != BooleanOperationType.INTERSECT: raise ValueError( "Cannot use a boolean operation other than 'INTERSECT' with a " - "CutoffSelectionRule." + "CutOffSelectionRule." ) self._operation_type = value @@ -213,7 +213,7 @@ def _from_pb_object( VariableOffsetSelectionRule, ] if not isinstance(parent_object, BooleanSelectionRule): - allowed_types_list += [CutoffSelectionRule, BooleanSelectionRule] + allowed_types_list += [CutOffSelectionRule, BooleanSelectionRule] allowed_types = tuple(allowed_types_list) selection_rule = tree_object_from_resource_path( diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 947dd24d73..c11ba8a4a4 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -94,7 +94,7 @@ from .base import ServerWrapper, TreeObject from .boolean_selection_rule import BooleanSelectionRule from .cad_geometry import CADGeometry -from .cutoff_selection_rule import CutoffSelectionRule +from .cut_off_selection_rule import CutOffSelectionRule from .cylindrical_selection_rule import CylindricalSelectionRule from .edge_set import EdgeSet from .element_set import ElementSet @@ -855,14 +855,14 @@ def export_modeling_ply_geometries( TubeSelectionRule, tube_selection_rule_pb2_grpc.ObjectServiceStub ) - create_cutoff_selection_rule = define_create_method( - CutoffSelectionRule, - func_name="create_cutoff_selection_rule", + create_cut_off_selection_rule = define_create_method( + CutOffSelectionRule, + func_name="create_cut_off_selection_rule", parent_class_name="Model", module_name=__module__, ) - cutoff_selection_rules = define_mutable_mapping( - CutoffSelectionRule, cutoff_selection_rule_pb2_grpc.ObjectServiceStub + cut_off_selection_rules = define_mutable_mapping( + CutOffSelectionRule, cutoff_selection_rule_pb2_grpc.ObjectServiceStub ) create_geometrical_selection_rule = define_create_method( diff --git a/src/ansys/acp/core/_tree_objects/solid_model.py b/src/ansys/acp/core/_tree_objects/solid_model.py index 7b106ec93a..32c36c3b81 100644 --- a/src/ansys/acp/core/_tree_objects/solid_model.py +++ b/src/ansys/acp/core/_tree_objects/solid_model.py @@ -120,14 +120,14 @@ class DropOffSettings(TreeObjectAttributeWithCache): drop_off_type : Determines whether the ply's drop-off is inside or outside the boundary of the ply. - disable_dropoffs_on_bottom : + disable_drop_offs_on_bottom : Whether to remove drop-offs on the bottom surface of the laminate. - dropoff_disabled_on_bottom_sets : + drop_off_disabled_on_bottom_sets : Element sets or oriented selection sets on which drop-offs at the bottom surface are disabled. - disable_dropoffs_on_top : + disable_drop_offs_on_top : Whether to remove drop-offs on the top surface of the laminate. - dropoff_disabled_on_top_sets : + drop_off_disabled_on_top_sets : Element sets or oriented selection sets on which drop-offs at the top surface are disabled. connect_butt_joined_plies : @@ -141,10 +141,10 @@ def __init__( self, *, drop_off_type: DropOffType = DropOffType.INSIDE_PLY, - disable_dropoffs_on_bottom: bool = False, - dropoff_disabled_on_bottom_sets: Iterable[ElementSet | OrientedSelectionSet] = (), - disable_dropoffs_on_top: bool = False, - dropoff_disabled_on_top_sets: Iterable[ElementSet | OrientedSelectionSet] = (), + disable_drop_offs_on_bottom: bool = False, + drop_off_disabled_on_bottom_sets: Iterable[ElementSet | OrientedSelectionSet] = (), + disable_drop_offs_on_top: bool = False, + drop_off_disabled_on_top_sets: Iterable[ElementSet | OrientedSelectionSet] = (), connect_butt_joined_plies: bool = True, _parent_object: SolidModel | None = None, _pb_object: Any | None = None, @@ -162,10 +162,10 @@ def __init__( # values. if _parent_object is None and _pb_object is None: self.drop_off_type = drop_off_type - self.disable_dropoffs_on_bottom = disable_dropoffs_on_bottom - self.dropoff_disabled_on_bottom_sets = dropoff_disabled_on_bottom_sets - self.disable_dropoffs_on_top = disable_dropoffs_on_top - self.dropoff_disabled_on_top_sets = dropoff_disabled_on_top_sets + self.disable_drop_offs_on_bottom = disable_drop_offs_on_bottom + self.drop_off_disabled_on_bottom_sets = drop_off_disabled_on_bottom_sets + self.disable_drop_offs_on_top = disable_drop_offs_on_top + self.drop_off_disabled_on_top_sets = drop_off_disabled_on_top_sets self.connect_butt_joined_plies = connect_butt_joined_plies @classmethod @@ -189,17 +189,17 @@ def _create_default_pb_object(self) -> solid_model_pb2.DropOffSettings: to_protobuf=drop_off_type_to_pb, ) - disable_dropoffs_on_bottom: ReadWriteProperty[bool, bool] = grpc_data_property( + disable_drop_offs_on_bottom: ReadWriteProperty[bool, bool] = grpc_data_property( "disable_dropoffs_on_bottom" ) - dropoff_disabled_on_bottom_sets = define_polymorphic_linked_object_list( + drop_off_disabled_on_bottom_sets = define_polymorphic_linked_object_list( "dropoff_disabled_on_bottom_sets", allowed_types=(ElementSet, OrientedSelectionSet) ) - disable_dropoffs_on_top: ReadWriteProperty[bool, bool] = grpc_data_property( + disable_drop_offs_on_top: ReadWriteProperty[bool, bool] = grpc_data_property( "disable_dropoffs_on_top" ) - dropoff_disabled_on_top_sets = define_polymorphic_linked_object_list( + drop_off_disabled_on_top_sets = define_polymorphic_linked_object_list( "dropoff_disabled_on_top_sets", allowed_types=(ElementSet, OrientedSelectionSet) ) diff --git a/src/ansys/acp/core/_tree_objects/stackup.py b/src/ansys/acp/core/_tree_objects/stackup.py index aeb33a82f9..031ecfe599 100644 --- a/src/ansys/acp/core/_tree_objects/stackup.py +++ b/src/ansys/acp/core/_tree_objects/stackup.py @@ -44,9 +44,9 @@ ) from .base import CreatableTreeObject, IdTreeObject from .enums import ( - CutoffMaterialHandling, + CutOffMaterialHandling, DrapingMaterialModel, - DropoffMaterialHandling, + DropOffMaterialHandling, SymmetryType, cut_off_material_type_from_pb, cut_off_material_type_to_pb, @@ -197,10 +197,10 @@ def __init__( topdown: bool = True, fabrics: Sequence[FabricWithAngle] = tuple(), area_price: float = 0.0, - drop_off_material_handling: DropoffMaterialHandling = "global", + drop_off_material_handling: DropOffMaterialHandling = "global", drop_off_material: Material | None = None, cut_off_material: Material | None = None, - cut_off_material_handling: CutoffMaterialHandling = "computed", + cut_off_material_handling: CutOffMaterialHandling = "computed", draping_material_model: DrapingMaterialModel = "woven", draping_ud_coefficient: float = 0.0, ): @@ -210,9 +210,9 @@ def __init__( self.topdown = topdown self.area_price = area_price self.fabrics = fabrics - self.drop_off_material_handling = DropoffMaterialHandling(drop_off_material_handling) + self.drop_off_material_handling = DropOffMaterialHandling(drop_off_material_handling) self.drop_off_material = drop_off_material - self.cut_off_material_handling = CutoffMaterialHandling(cut_off_material_handling) + self.cut_off_material_handling = CutOffMaterialHandling(cut_off_material_handling) self.cut_off_material = cut_off_material self.draping_material_model = DrapingMaterialModel(draping_material_model) self.draping_ud_coefficient = draping_ud_coefficient diff --git a/src/ansys/acp/core/mesh_data.py b/src/ansys/acp/core/mesh_data.py index 1d235fcf33..58ec11a016 100644 --- a/src/ansys/acp/core/mesh_data.py +++ b/src/ansys/acp/core/mesh_data.py @@ -30,8 +30,8 @@ AnalysisPlyNodalData, BooleanSelectionRuleElementalData, BooleanSelectionRuleNodalData, - CutoffSelectionRuleElementalData, - CutoffSelectionRuleNodalData, + CutOffSelectionRuleElementalData, + CutOffSelectionRuleNodalData, CylindricalSelectionRuleElementalData, CylindricalSelectionRuleNodalData, ElementSetElementalData, @@ -71,8 +71,8 @@ "AnalysisPlyNodalData", "BooleanSelectionRuleElementalData", "BooleanSelectionRuleNodalData", - "CutoffSelectionRuleElementalData", - "CutoffSelectionRuleNodalData", + "CutOffSelectionRuleElementalData", + "CutOffSelectionRuleNodalData", "CylindricalSelectionRuleElementalData", "CylindricalSelectionRuleNodalData", "ElementSetElementalData", diff --git a/tests/unittests/test_cutoff_selection_rule.py b/tests/unittests/test_cut_off_selection_rule.py similarity index 70% rename from tests/unittests/test_cutoff_selection_rule.py rename to tests/unittests/test_cut_off_selection_rule.py index 2157117777..3d996270e2 100644 --- a/tests/unittests/test_cutoff_selection_rule.py +++ b/tests/unittests/test_cut_off_selection_rule.py @@ -22,8 +22,8 @@ import pytest -from ansys.acp.core import CutoffRuleType, PlyCutoffType -from ansys.acp.core.mesh_data import CutoffSelectionRuleElementalData, CutoffSelectionRuleNodalData +from ansys.acp.core import CutOffRuleType, PlyCutOffType +from ansys.acp.core.mesh_data import CutOffSelectionRuleElementalData, CutOffSelectionRuleNodalData from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester @@ -36,27 +36,27 @@ def parent_object(load_model_from_tempfile): @pytest.fixture def tree_object(parent_object): - return parent_object.create_cutoff_selection_rule() + return parent_object.create_cut_off_selection_rule() -class TestCutoffSelectionRule(NoLockedMixin, TreeObjectTester): - COLLECTION_NAME = "cutoff_selection_rules" +class TestCutOffSelectionRule(NoLockedMixin, TreeObjectTester): + COLLECTION_NAME = "cut_off_selection_rules" @staticmethod @pytest.fixture def default_properties(): return { "status": "NOTUPTODATE", - "cutoff_rule_type": CutoffRuleType.GEOMETRY, - "cutoff_geometry": None, + "cut_off_rule_type": CutOffRuleType.GEOMETRY, + "cut_off_geometry": None, "taper_edge_set": None, "offset": 0.0, "angle": 0.0, - "ply_cutoff_type": PlyCutoffType.PRODUCTION_PLY_CUTOFF, + "ply_cut_off_type": PlyCutOffType.PRODUCTION_PLY_CUTOFF, "ply_tapering": False, } - CREATE_METHOD_NAME = "create_cutoff_selection_rule" + CREATE_METHOD_NAME = "create_cut_off_selection_rule" @staticmethod @pytest.fixture @@ -66,13 +66,13 @@ def object_properties(parent_object): edge_set = model.create_edge_set() return ObjectPropertiesToTest( read_write=[ - ("name", "Cutoff Selection Rule name"), - ("cutoff_rule_type", CutoffRuleType.TAPER), - ("cutoff_geometry", geometry), + ("name", "CutOff Selection Rule name"), + ("cut_off_rule_type", CutOffRuleType.TAPER), + ("cut_off_geometry", geometry), ("taper_edge_set", edge_set), ("offset", 1.2), ("angle", 2.3), - ("ply_cutoff_type", PlyCutoffType.ANALYSIS_PLY_CUTOFF), + ("ply_cut_off_type", PlyCutOffType.ANALYSIS_PLY_CUTOFF), ("ply_tapering", True), ], read_only=[ @@ -83,6 +83,6 @@ def object_properties(parent_object): def test_mesh_data(parent_object): - rule = parent_object.create_cutoff_selection_rule() - assert isinstance(rule.elemental_data, CutoffSelectionRuleElementalData) - assert isinstance(rule.nodal_data, CutoffSelectionRuleNodalData) + rule = parent_object.create_cut_off_selection_rule() + assert isinstance(rule.elemental_data, CutOffSelectionRuleElementalData) + assert isinstance(rule.nodal_data, CutOffSelectionRuleNodalData) diff --git a/tests/unittests/test_fabric.py b/tests/unittests/test_fabric.py index 70b251c591..b4b8fa198a 100644 --- a/tests/unittests/test_fabric.py +++ b/tests/unittests/test_fabric.py @@ -23,7 +23,7 @@ from packaging.version import parse as parse_version import pytest -from ansys.acp.core import CutoffMaterialHandling, DrapingMaterialModel, DropoffMaterialHandling +from ansys.acp.core import CutOffMaterialHandling, DrapingMaterialModel, DropOffMaterialHandling from .common.tree_object_tester import NoLockedMixin, ObjectPropertiesToTest, TreeObjectTester @@ -51,8 +51,8 @@ def default_properties(acp_instance): "thickness": 0.0, "area_price": 0.0, "ignore_for_postprocessing": False, - "drop_off_material_handling": DropoffMaterialHandling.GLOBAL, - "cut_off_material_handling": CutoffMaterialHandling.COMPUTED, + "drop_off_material_handling": DropOffMaterialHandling.GLOBAL, + "cut_off_material_handling": CutOffMaterialHandling.COMPUTED, "draping_material_model": DrapingMaterialModel.WOVEN, "draping_ud_coefficient": 0.0, "material": None, @@ -63,9 +63,9 @@ def default_properties(acp_instance): "thickness": 0.0, "area_price": 0.0, "ignore_for_postprocessing": False, - "drop_off_material_handling": DropoffMaterialHandling.GLOBAL, + "drop_off_material_handling": DropOffMaterialHandling.GLOBAL, "drop_off_material": None, - "cut_off_material_handling": CutoffMaterialHandling.COMPUTED, + "cut_off_material_handling": CutOffMaterialHandling.COMPUTED, "cut_off_material": None, "draping_material_model": DrapingMaterialModel.WOVEN, "draping_ud_coefficient": 0.0, @@ -88,8 +88,8 @@ def object_properties(parent_object, acp_instance): ("thickness", 1e-6), ("area_price", 5.98), ("ignore_for_postprocessing", True), - ("drop_off_material_handling", DropoffMaterialHandling.GLOBAL), - ("cut_off_material_handling", CutoffMaterialHandling.COMPUTED), + ("drop_off_material_handling", DropOffMaterialHandling.GLOBAL), + ("cut_off_material_handling", CutOffMaterialHandling.COMPUTED), ("draping_material_model", DrapingMaterialModel.UD), ("draping_ud_coefficient", 0.55), ("material", material), @@ -110,9 +110,9 @@ def object_properties(parent_object, acp_instance): ("thickness", 1e-6), ("area_price", 5.98), ("ignore_for_postprocessing", True), - ("drop_off_material_handling", DropoffMaterialHandling.CUSTOM), + ("drop_off_material_handling", DropOffMaterialHandling.CUSTOM), ("drop_off_material", drop_off_material), - ("cut_off_material_handling", CutoffMaterialHandling.CUSTOM), + ("cut_off_material_handling", CutOffMaterialHandling.CUSTOM), ("cut_off_material", cut_off_material), ("draping_material_model", DrapingMaterialModel.UD), ("draping_ud_coefficient", 0.55), @@ -131,8 +131,8 @@ def object_properties(parent_object, acp_instance): @pytest.mark.parametrize("material_type", ["cut_off_material", "drop_off_material"]) def test_solid_model_materials(parent_object, tree_object, acp_instance, material_type): """Check that solid model materials are supported since 25.1.""" - tree_object.cut_off_material_handling = CutoffMaterialHandling.CUSTOM - tree_object.drop_off_material_handling = DropoffMaterialHandling.CUSTOM + tree_object.cut_off_material_handling = CutOffMaterialHandling.CUSTOM + tree_object.drop_off_material_handling = DropOffMaterialHandling.CUSTOM if parse_version(acp_instance.server_version) < parse_version("25.1"): with pytest.raises(RuntimeError) as exc: setattr(tree_object, material_type, parent_object.create_material(name="Material")) diff --git a/tests/unittests/test_modeling_ply.py b/tests/unittests/test_modeling_ply.py index b676db551a..84878e3425 100644 --- a/tests/unittests/test_modeling_ply.py +++ b/tests/unittests/test_modeling_ply.py @@ -26,7 +26,7 @@ from ansys.acp.core import ( BooleanOperationType, - CutoffSelectionRule, + CutOffSelectionRule, DrapingType, ElementalDataType, Fabric, @@ -148,7 +148,7 @@ def object_properties(request, parent_model): parameter_2=0.0, ), LinkedSelectionRule( - selection_rule=parent_model.create_cutoff_selection_rule(), + selection_rule=parent_model.create_cut_off_selection_rule(), operation_type=BooleanOperationType.INTERSECT, template_rule=True, parameter_1=1.2, @@ -447,11 +447,11 @@ def test_linked_selection_rule_parameters(simple_modeling_ply, minimal_complete_ @pytest.mark.parametrize( "operation_type", [e for e in BooleanOperationType if e != BooleanOperationType.INTERSECT] ) -def test_linked_cutoff_selection_rule_operation_type(operation_type): - """Check that CutoffSelectionRule only allows INTERSECT operation type.""" +def test_linked_cut_off_selection_rule_operation_type(operation_type): + """Check that CutOffSelectionRule only allows INTERSECT operation type.""" with pytest.raises(ValueError) as exc: LinkedSelectionRule( - selection_rule=CutoffSelectionRule(), + selection_rule=CutOffSelectionRule(), operation_type=operation_type, ) assert "INTERSECT" in str(exc.value) diff --git a/tests/unittests/test_solid_model.py b/tests/unittests/test_solid_model.py index 3347870d4f..d6cd0eaf01 100644 --- a/tests/unittests/test_solid_model.py +++ b/tests/unittests/test_solid_model.py @@ -105,13 +105,13 @@ def object_properties(parent_object): PropertyWithCustomComparison( initial_value=pyacp.DropOffSettings( drop_off_type=pyacp.DropOffType.OUTSIDE_PLY, - disable_dropoffs_on_bottom=True, - dropoff_disabled_on_bottom_sets=[ + disable_drop_offs_on_bottom=True, + drop_off_disabled_on_bottom_sets=[ model.create_element_set(), model.create_oriented_selection_set(), ], - disable_dropoffs_on_top=True, - dropoff_disabled_on_top_sets=[ + disable_drop_offs_on_top=True, + drop_off_disabled_on_top_sets=[ model.create_oriented_selection_set(), model.create_element_set(), ], @@ -155,83 +155,83 @@ def object_properties(parent_object): @given( - disable_dropoffs_on_bottom=st.booleans(), - disable_dropoffs_on_top=st.booleans(), + disable_drop_offs_on_bottom=st.booleans(), + disable_drop_offs_on_top=st.booleans(), connect_butt_joined_plies=st.booleans(), ) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) def test_drop_off_settings_on_init( - parent_object, disable_dropoffs_on_bottom, disable_dropoffs_on_top, connect_butt_joined_plies + parent_object, disable_drop_offs_on_bottom, disable_drop_offs_on_top, connect_butt_joined_plies ): """Check that the drop-off settings are correctly set when passed to the SolidModel constructor.""" - dropoff_disabled_on_bottom_sets = [ + drop_off_disabled_on_bottom_sets = [ parent_object.create_element_set(), parent_object.create_oriented_selection_set(), ] - dropoff_disabled_on_top_sets = [ + drop_off_disabled_on_top_sets = [ parent_object.create_oriented_selection_set(), parent_object.create_element_set(), ] drop_off_settings = pyacp.DropOffSettings( - disable_dropoffs_on_bottom=disable_dropoffs_on_bottom, - dropoff_disabled_on_bottom_sets=dropoff_disabled_on_bottom_sets, - disable_dropoffs_on_top=disable_dropoffs_on_top, - dropoff_disabled_on_top_sets=dropoff_disabled_on_top_sets, + disable_drop_offs_on_bottom=disable_drop_offs_on_bottom, + drop_off_disabled_on_bottom_sets=drop_off_disabled_on_bottom_sets, + disable_drop_offs_on_top=disable_drop_offs_on_top, + drop_off_disabled_on_top_sets=drop_off_disabled_on_top_sets, connect_butt_joined_plies=connect_butt_joined_plies, ) solid_model = parent_object.create_solid_model(drop_off_settings=drop_off_settings) - assert solid_model.drop_off_settings.disable_dropoffs_on_bottom == disable_dropoffs_on_bottom - assert solid_model.drop_off_settings.disable_dropoffs_on_top == disable_dropoffs_on_top + assert solid_model.drop_off_settings.disable_drop_offs_on_bottom == disable_drop_offs_on_bottom + assert solid_model.drop_off_settings.disable_drop_offs_on_top == disable_drop_offs_on_top assert solid_model.drop_off_settings.connect_butt_joined_plies == connect_butt_joined_plies assert ( - solid_model.drop_off_settings.dropoff_disabled_on_bottom_sets - == dropoff_disabled_on_bottom_sets + solid_model.drop_off_settings.drop_off_disabled_on_bottom_sets + == drop_off_disabled_on_bottom_sets ) assert ( - solid_model.drop_off_settings.dropoff_disabled_on_top_sets == dropoff_disabled_on_top_sets + solid_model.drop_off_settings.drop_off_disabled_on_top_sets == drop_off_disabled_on_top_sets ) @given( - disable_dropoffs_on_bottom=st.booleans(), - disable_dropoffs_on_top=st.booleans(), + disable_drop_offs_on_bottom=st.booleans(), + disable_drop_offs_on_top=st.booleans(), connect_butt_joined_plies=st.booleans(), ) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) def test_drop_off_settings_assign( - parent_object, disable_dropoffs_on_bottom, disable_dropoffs_on_top, connect_butt_joined_plies + parent_object, disable_drop_offs_on_bottom, disable_drop_offs_on_top, connect_butt_joined_plies ): """Check that the drop-off settings are correctly set when assigned to the SolidModel.""" - dropoff_disabled_on_bottom_sets = [ + drop_off_disabled_on_bottom_sets = [ parent_object.create_element_set(), parent_object.create_oriented_selection_set(), ] - dropoff_disabled_on_top_sets = [ + drop_off_disabled_on_top_sets = [ parent_object.create_oriented_selection_set(), parent_object.create_element_set(), ] drop_off_settings = pyacp.DropOffSettings( - disable_dropoffs_on_bottom=disable_dropoffs_on_bottom, - dropoff_disabled_on_bottom_sets=dropoff_disabled_on_bottom_sets, - disable_dropoffs_on_top=disable_dropoffs_on_top, - dropoff_disabled_on_top_sets=dropoff_disabled_on_top_sets, + disable_drop_offs_on_bottom=disable_drop_offs_on_bottom, + drop_off_disabled_on_bottom_sets=drop_off_disabled_on_bottom_sets, + disable_drop_offs_on_top=disable_drop_offs_on_top, + drop_off_disabled_on_top_sets=drop_off_disabled_on_top_sets, connect_butt_joined_plies=connect_butt_joined_plies, ) solid_model = parent_object.create_solid_model() solid_model.drop_off_settings = drop_off_settings - assert solid_model.drop_off_settings.disable_dropoffs_on_bottom == disable_dropoffs_on_bottom - assert solid_model.drop_off_settings.disable_dropoffs_on_top == disable_dropoffs_on_top + assert solid_model.drop_off_settings.disable_drop_offs_on_bottom == disable_drop_offs_on_bottom + assert solid_model.drop_off_settings.disable_drop_offs_on_top == disable_drop_offs_on_top assert solid_model.drop_off_settings.connect_butt_joined_plies == connect_butt_joined_plies assert ( - solid_model.drop_off_settings.dropoff_disabled_on_bottom_sets - == dropoff_disabled_on_bottom_sets + solid_model.drop_off_settings.drop_off_disabled_on_bottom_sets + == drop_off_disabled_on_bottom_sets ) assert ( - solid_model.drop_off_settings.dropoff_disabled_on_top_sets == dropoff_disabled_on_top_sets + solid_model.drop_off_settings.drop_off_disabled_on_top_sets == drop_off_disabled_on_top_sets ) @@ -246,21 +246,21 @@ def test_drop_off_settings_assign_wrong_type(parent_object): def test_drop_off_settings_string_representation(parent_object): """Check that the string representation of the drop-off settings is correct.""" solid_model = parent_object.create_solid_model() - dropoff_disabled_on_bottom_sets = [ + drop_off_disabled_on_bottom_sets = [ parent_object.create_element_set(), parent_object.create_oriented_selection_set(), ] - dropoff_disabled_on_top_sets = [ + drop_off_disabled_on_top_sets = [ parent_object.create_oriented_selection_set(), parent_object.create_element_set(), ] drop_off_settings = pyacp.DropOffSettings( drop_off_type=pyacp.DropOffType.OUTSIDE_PLY, - disable_dropoffs_on_bottom=True, - dropoff_disabled_on_bottom_sets=dropoff_disabled_on_bottom_sets, - disable_dropoffs_on_top=True, - dropoff_disabled_on_top_sets=dropoff_disabled_on_top_sets, + disable_drop_offs_on_bottom=True, + drop_off_disabled_on_bottom_sets=drop_off_disabled_on_bottom_sets, + disable_drop_offs_on_top=True, + drop_off_disabled_on_top_sets=drop_off_disabled_on_top_sets, connect_butt_joined_plies=False, ) # When the drop-off settings are not yet linked to the server, the linked @@ -272,7 +272,7 @@ def test_drop_off_settings_string_representation(parent_object): solid_model.drop_off_settings = drop_off_settings str_repr = str(solid_model.drop_off_settings) assert "DropOffSettings" in str_repr - for val in dropoff_disabled_on_bottom_sets + dropoff_disabled_on_top_sets: + for val in drop_off_disabled_on_bottom_sets + drop_off_disabled_on_top_sets: assert repr(val) in str_repr diff --git a/tests/unittests/test_stackup.py b/tests/unittests/test_stackup.py index 036b89d391..b28ced9271 100644 --- a/tests/unittests/test_stackup.py +++ b/tests/unittests/test_stackup.py @@ -23,9 +23,9 @@ import pytest from ansys.acp.core import ( - CutoffMaterialHandling, + CutOffMaterialHandling, DrapingMaterialModel, - DropoffMaterialHandling, + DropOffMaterialHandling, FabricWithAngle, SymmetryType, ) @@ -56,9 +56,9 @@ def default_properties(): "topdown": True, "fabrics": [], "symmetry": SymmetryType.NO_SYMMETRY, - "drop_off_material_handling": DropoffMaterialHandling.GLOBAL, + "drop_off_material_handling": DropOffMaterialHandling.GLOBAL, "drop_off_material": None, - "cut_off_material_handling": CutoffMaterialHandling.COMPUTED, + "cut_off_material_handling": CutOffMaterialHandling.COMPUTED, "cut_off_material": None, "draping_material_model": DrapingMaterialModel.WOVEN, "draping_ud_coefficient": 0.0, @@ -86,9 +86,9 @@ def object_properties(parent_object): ], ), ("symmetry", SymmetryType.EVEN_SYMMETRY), - ("drop_off_material_handling", DropoffMaterialHandling.CUSTOM), + ("drop_off_material_handling", DropOffMaterialHandling.CUSTOM), ("drop_off_material", material), - ("cut_off_material_handling", CutoffMaterialHandling.CUSTOM), + ("cut_off_material_handling", CutOffMaterialHandling.CUSTOM), ("cut_off_material", material), ("draping_material_model", DrapingMaterialModel.UD), ("draping_ud_coefficient", 0.55), diff --git a/tests/unittests/test_tree_printer.py b/tests/unittests/test_tree_printer.py index ad53f495e6..d3ebe4d131 100644 --- a/tests/unittests/test_tree_printer.py +++ b/tests/unittests/test_tree_printer.py @@ -73,7 +73,7 @@ def case_more_objects(acp_instance, model_data_dir): model.create_parallel_selection_rule() model.create_cylindrical_selection_rule() model.create_tube_selection_rule() - model.create_cutoff_selection_rule() + model.create_cut_off_selection_rule() model.create_geometrical_selection_rule() model.create_boolean_selection_rule() model.create_lookup_table_1d() @@ -116,8 +116,8 @@ def case_more_objects(acp_instance, model_data_dir): 'CylindricalSelectionrule' Tube Selection Rules 'TubeSelectionrule' - Cutoff Selection Rules - 'CutoffSelectionrule' + Cut Off Selection Rules + 'CutOffSelectionrule' Geometrical Selection Rules 'GeometricalSelectionrule' Boolean Selection Rules diff --git a/type_checks/add_methods.py b/type_checks/add_methods.py index 552e077547..7d06eb821c 100644 --- a/type_checks/add_methods.py +++ b/type_checks/add_methods.py @@ -7,7 +7,7 @@ from ansys.acp.core import ( BooleanOperationType, BooleanSelectionRule, - CutoffSelectionRule, + CutOffSelectionRule, CylindricalSelectionRule, GeometricalSelectionRule, LinkedSelectionRule, @@ -30,7 +30,7 @@ Arg( Union[ BooleanSelectionRule, - CutoffSelectionRule, + CutOffSelectionRule, CylindricalSelectionRule, GeometricalSelectionRule, ParallelSelectionRule, From 751e3934eacedcd155a1067a559e50962a9a42e6 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 28 Nov 2024 20:40:19 +0100 Subject: [PATCH 88/96] Rename DimensionType to PhysicalDimension (#716) Partially addresses #637. --- doc/source/api/enum_types.rst | 10 ++++----- .../03-advanced-selection-rules.py | 4 ++-- .../04-layup-thickness-definitions.py | 6 ++++-- .../06-ply-direction-lookup-table.py | 8 +++---- src/ansys/acp/core/__init__.py | 10 ++++----- src/ansys/acp/core/_tree_objects/__init__.py | 6 +++--- src/ansys/acp/core/_tree_objects/enums.py | 18 ++++++++-------- .../_tree_objects/lookup_table_1d_column.py | 10 +++++---- .../_tree_objects/lookup_table_3d_column.py | 10 +++++---- .../_tree_objects/lookup_table_column_base.py | 21 +++++++++++-------- tests/unittests/test_lookup_table_column.py | 12 +++++------ 11 files changed, 62 insertions(+), 53 deletions(-) diff --git a/doc/source/api/enum_types.rst b/doc/source/api/enum_types.rst index f725fd0875..4bcdf2c004 100644 --- a/doc/source/api/enum_types.rst +++ b/doc/source/api/enum_types.rst @@ -13,7 +13,6 @@ Enumeration data types CutOffGeometryOrientationType CutOffMaterialHandling CutOffRuleType - DimensionType DrapingMaterialModel DrapingType DropOffMaterialHandling @@ -26,6 +25,8 @@ Enumeration data types ExtrusionType FeFormat GeometricalRuleType + HDF5CompositeCAEImportMode + HDF5CompositeCAEProjectionMode IgnorableEntity ImportedPlyDrapingType ImportedPlyOffsetType @@ -34,14 +35,11 @@ Enumeration data types LayupMappingRosetteSelectionMethod LinkedObjectHandling LookUpTable3DInterpolationAlgorithm - HDF5CompositeCAEImportMode - HDF5CompositeCAEProjectionMode LookUpTableColumnValueType MeshImportType NodalDataType - SolidModelOffsetDirectionType OffsetType - SnapToGeometryOrientationType + PhysicalDimension PlyCutOffType PlyGeometryExportFormat PlyType @@ -50,8 +48,10 @@ Enumeration data types RosetteType SectionCutType SensorType + SnapToGeometryOrientationType SolidModelExportFormat SolidModelImportFormat + SolidModelOffsetDirectionType SolidModelSkinExportFormat Status StressStateType diff --git a/examples/modeling_features/03-advanced-selection-rules.py b/examples/modeling_features/03-advanced-selection-rules.py index 38d13638c8..f44580b6e4 100644 --- a/examples/modeling_features/03-advanced-selection-rules.py +++ b/examples/modeling_features/03-advanced-selection-rules.py @@ -51,9 +51,9 @@ # Import the PyACP dependencies. from ansys.acp.core import ( BooleanOperationType, - DimensionType, EdgeSetType, LinkedSelectionRule, + PhysicalDimension, launch_acp, ) from ansys.acp.core.extras import ExampleKeys, get_example_file @@ -241,7 +241,7 @@ # Create the offset column that defines the offsets from the edge. offsets_column = lookup_table.create_column( name="offset", - dimension_type=DimensionType.LENGTH, + physical_dimension=PhysicalDimension.LENGTH, data=np.array([0.00, 0.004, 0]), ) diff --git a/examples/modeling_features/04-layup-thickness-definitions.py b/examples/modeling_features/04-layup-thickness-definitions.py index b5db53c001..f44fb0db9c 100644 --- a/examples/modeling_features/04-layup-thickness-definitions.py +++ b/examples/modeling_features/04-layup-thickness-definitions.py @@ -45,7 +45,7 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import DimensionType, ThicknessType, launch_acp +from ansys.acp.core import PhysicalDimension, ThicknessType, launch_acp from ansys.acp.core.extras import FLAT_PLATE_SOLID_CAMERA, ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 2 @@ -166,7 +166,9 @@ # Create the lookup table and add the coordinates and thickness data. lookup_table = model.create_lookup_table_3d() lookup_table.columns["Location"].data = points -thickness_column = lookup_table.create_column(data=thickness, dimension_type=DimensionType.LENGTH) +thickness_column = lookup_table.create_column( + data=thickness, physical_dimension=PhysicalDimension.LENGTH +) # %% # Set the thickness type to ``FROM_TABLE`` and assign the thickness column. diff --git a/examples/modeling_features/06-ply-direction-lookup-table.py b/examples/modeling_features/06-ply-direction-lookup-table.py index 72e5d910f3..82df50cba2 100644 --- a/examples/modeling_features/06-ply-direction-lookup-table.py +++ b/examples/modeling_features/06-ply-direction-lookup-table.py @@ -45,9 +45,9 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import ( - DimensionType, DrapingType, LookUpTableColumnValueType, + PhysicalDimension, PlyType, RosetteSelectionMethod, get_directions_plotter, @@ -147,7 +147,7 @@ lookup_table.columns["Location"].data = points direction_column = lookup_table.create_column( data=directions, - dimension_type=DimensionType.DIMENSIONLESS, + physical_dimension=PhysicalDimension.DIMENSIONLESS, value_type=LookUpTableColumnValueType.DIRECTION, ) @@ -177,7 +177,7 @@ correction_angle = np.arctan2(xx.ravel(), zz.ravel()) * 180 / np.pi angle_column_1 = lookup_table.create_column( data=correction_angle, - dimension_type=DimensionType.DIMENSIONLESS, + physical_dimension=PhysicalDimension.DIMENSIONLESS, value_type=LookUpTableColumnValueType.SCALAR, ) @@ -187,7 +187,7 @@ transverse_correction_angle = correction_angle + shear_angle angle_column_2 = lookup_table.create_column( data=transverse_correction_angle, - dimension_type=DimensionType.DIMENSIONLESS, + physical_dimension=PhysicalDimension.DIMENSIONLESS, value_type=LookUpTableColumnValueType.SCALAR, ) diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 5d793e7944..16fd100fca 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -61,7 +61,6 @@ CutOffRuleType, CutOffSelectionRule, CylindricalSelectionRule, - DimensionType, DrapingMaterialModel, DrapingType, DropOffMaterialHandling, @@ -115,6 +114,7 @@ OffsetType, OrientedSelectionSet, ParallelSelectionRule, + PhysicalDimension, PlyCutOffType, PlyGeometryExportFormat, PlyType, @@ -179,9 +179,9 @@ "CutOffRuleType", "CutOffSelectionRule", "CylindricalSelectionRule", - "DimensionType", "DirectLaunchConfig", "DockerComposeLaunchConfig", + "dpf_integration_helpers", "DrapingMaterialModel", "DrapingType", "DropOffMaterialHandling", @@ -236,7 +236,7 @@ "material_property_sets", "Material", "mechanical_integration_helpers", - "dpf_integration_helpers", + "mesh_data", "MeshImportType", "Model", "ModelingGroup", @@ -245,9 +245,9 @@ "OffsetType", "OrientedSelectionSet", "ParallelSelectionRule", + "PhysicalDimension", "PlyCutOffType", "PlyGeometryExportFormat", - "SolidModelOffsetDirectionType", "PlyType", "PrimaryPly", "print_model", @@ -271,6 +271,7 @@ "SolidModelExportFormat", "SolidModelExportSettings", "SolidModelImportFormat", + "SolidModelOffsetDirectionType", "SolidModelSkinExportFormat", "SphericalSelectionRule", "Stackup", @@ -287,5 +288,4 @@ "VariableOffsetSelectionRule", "VirtualGeometry", "VirtualGeometryDimension", - "mesh_data", ] diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 3559a48a2f..7604c9c27f 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -51,7 +51,6 @@ CutOffGeometryOrientationType, CutOffMaterialHandling, CutOffRuleType, - DimensionType, DrapingMaterialModel, DrapingType, DropOffMaterialHandling, @@ -72,6 +71,7 @@ MeshImportType, NodalDataType, OffsetType, + PhysicalDimension, PlyCutOffType, PlyGeometryExportFormat, PlyType, @@ -204,7 +204,6 @@ "CylindricalSelectionRule", "CylindricalSelectionRuleElementalData", "CylindricalSelectionRuleNodalData", - "DimensionType", "DrapingMaterialModel", "DrapingType", "DropOffMaterialHandling", @@ -268,7 +267,6 @@ "ModelingPlyNodalData", "ModelNodalData", "NodalDataType", - "SolidModelOffsetDirectionType", "OffsetType", "OrientedSelectionSet", "OrientedSelectionSetElementalData", @@ -276,6 +274,7 @@ "ParallelSelectionRule", "ParallelSelectionRuleElementalData", "ParallelSelectionRuleNodalData", + "PhysicalDimension", "PlyCutOffType", "PlyGeometryExportFormat", "PlyType", @@ -307,6 +306,7 @@ "SolidModelExportSettings", "SolidModelImportFormat", "SolidModelNodalData", + "SolidModelOffsetDirectionType", "SolidModelSkinExportFormat", "SphericalSelectionRule", "SphericalSelectionRuleElementalData", diff --git a/src/ansys/acp/core/_tree_objects/enums.py b/src/ansys/acp/core/_tree_objects/enums.py index dda255f9b1..7874dac778 100644 --- a/src/ansys/acp/core/_tree_objects/enums.py +++ b/src/ansys/acp/core/_tree_objects/enums.py @@ -49,11 +49,11 @@ __all__ = [ "ArrowType", + "BaseElementMaterialHandling", "BooleanOperationType", "CutOffGeometryOrientationType", "CutOffMaterialHandling", "CutOffRuleType", - "DimensionType", "DrapingMaterialModel", "DrapingType", "DropOffMaterialHandling", @@ -61,9 +61,6 @@ "EdgeSetType", "ElementalDataType", "ElementTechnology", - "ReinforcingBehavior", - "BaseElementMaterialHandling", - "StressStateType", "ExtrusionGuideType", "ExtrusionMethod", "ExtrusionType", @@ -75,19 +72,22 @@ "LookUpTable3DInterpolationAlgorithm", "LookUpTableColumnValueType", "NodalDataType", - "SolidModelOffsetDirectionType", "OffsetType", + "PhysicalDimension", "PlyCutOffType", "PlyGeometryExportFormat", "PlyType", + "ReinforcingBehavior", "RosetteSelectionMethod", "RosetteType", "SectionCutType", "SensorType", "SnapToGeometryOrientationType", "SolidModelExportFormat", + "SolidModelOffsetDirectionType", "SolidModelSkinExportFormat", "Status", + "StressStateType", "SymmetryType", "ThicknessFieldType", "ThicknessType", @@ -256,11 +256,11 @@ ) ( - DimensionType, - dimension_type_to_pb, - dimension_type_from_pb, + PhysicalDimension, + physical_dimension_to_pb, + physical_dimension_from_pb, ) = wrap_to_string_enum( - "DimensionType", + "PhysicalDimension", unit_system_pb2.DimensionType, module=__name__, doc="Options for the dimension (time, length, currency, ...) of data.", diff --git a/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py b/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py index 5ca0ab53fd..5273974d53 100644 --- a/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py +++ b/src/ansys/acp/core/_tree_objects/lookup_table_1d_column.py @@ -30,7 +30,7 @@ from ansys.api.acp.v0 import lookup_table_1d_column_pb2, lookup_table_1d_column_pb2_grpc from ._grpc_helpers.property_helper import mark_grpc_properties -from .enums import DimensionType, LookUpTableColumnValueType +from .enums import LookUpTableColumnValueType, PhysicalDimension from .lookup_table_column_base import LookUpTableColumnBase from .object_registry import register @@ -49,7 +49,7 @@ class LookUpTable1DColumn(LookUpTableColumnBase): directional (three entries per row). Note that the ``value_type`` can only be set when constructing the column, and is read-only afterwards. - dimension_type : + physical_dimension : Dimensionality (such as time, length, force, ...) of the column data. data : The column data. The shape of the data must match the ``value_type`` @@ -69,10 +69,12 @@ def __init__( *, name: str = "LookUpTable1DColumn", value_type: LookUpTableColumnValueType = LookUpTableColumnValueType.SCALAR, - dimension_type: DimensionType = DimensionType.DIMENSIONLESS, + physical_dimension: PhysicalDimension = PhysicalDimension.DIMENSIONLESS, data: npt.NDArray[np.float64] | None = None, ): - super().__init__(name=name, value_type=value_type, dimension_type=dimension_type, data=data) + super().__init__( + name=name, value_type=value_type, physical_dimension=physical_dimension, data=data + ) def _create_stub(self) -> lookup_table_1d_column_pb2_grpc.ObjectServiceStub: return lookup_table_1d_column_pb2_grpc.ObjectServiceStub(self._channel) diff --git a/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py b/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py index 29ecc9a504..24bf6c5a2a 100644 --- a/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py +++ b/src/ansys/acp/core/_tree_objects/lookup_table_3d_column.py @@ -30,7 +30,7 @@ from ansys.api.acp.v0 import lookup_table_3d_column_pb2, lookup_table_3d_column_pb2_grpc from ._grpc_helpers.property_helper import mark_grpc_properties -from .enums import DimensionType, LookUpTableColumnValueType +from .enums import LookUpTableColumnValueType, PhysicalDimension from .lookup_table_column_base import LookUpTableColumnBase from .object_registry import register @@ -49,7 +49,7 @@ class LookUpTable3DColumn(LookUpTableColumnBase): directional (three entries per row). Note that the ``value_type`` can only be set when constructing the column, and is read-only afterwards. - dimension_type : + physical_dimension : Dimensionality (such as time, length, force, ...) of the column data. data : The column data. The shape of the data must match the ``value_type`` @@ -69,10 +69,12 @@ def __init__( *, name: str = "LookUpTable3DColumn", value_type: LookUpTableColumnValueType = LookUpTableColumnValueType.SCALAR, - dimension_type: DimensionType = DimensionType.DIMENSIONLESS, + physical_dimension: PhysicalDimension = PhysicalDimension.DIMENSIONLESS, data: npt.NDArray[np.float64] | None = None, ): - super().__init__(name=name, value_type=value_type, dimension_type=dimension_type, data=data) + super().__init__( + name=name, value_type=value_type, physical_dimension=physical_dimension, data=data + ) def _create_stub(self) -> lookup_table_3d_column_pb2_grpc.ObjectServiceStub: return lookup_table_3d_column_pb2_grpc.ObjectServiceStub(self._channel) diff --git a/src/ansys/acp/core/_tree_objects/lookup_table_column_base.py b/src/ansys/acp/core/_tree_objects/lookup_table_column_base.py index 75ca05933d..b47e09d244 100644 --- a/src/ansys/acp/core/_tree_objects/lookup_table_column_base.py +++ b/src/ansys/acp/core/_tree_objects/lookup_table_column_base.py @@ -37,12 +37,12 @@ ) from .base import CreatableTreeObject, IdTreeObject from .enums import ( - DimensionType, LookUpTableColumnValueType, - dimension_type_from_pb, - dimension_type_to_pb, + PhysicalDimension, lookup_table_column_value_type_from_pb, lookup_table_column_value_type_to_pb, + physical_dimension_from_pb, + physical_dimension_to_pb, ) __all__ = ["LookUpTableColumnBase"] @@ -59,7 +59,7 @@ class LookUpTableColumnBase(CreatableTreeObject, IdTreeObject): directional (three entries per row). Note that the ``value_type`` can only be set when constructing the column, and is read-only afterwards. - dimension_type : + physical_dimension : Dimensionality (such as time, length, force, ...) of the column data. data : The column data. The shape of the data must match the ``value_type`` @@ -74,7 +74,7 @@ def __init__( *, name: str, value_type: LookUpTableColumnValueType = LookUpTableColumnValueType.SCALAR, - dimension_type: DimensionType = DimensionType.DIMENSIONLESS, + physical_dimension: PhysicalDimension = PhysicalDimension.DIMENSIONLESS, data: npt.NDArray[np.float64] | None = None, ): super().__init__(name=name) @@ -88,17 +88,20 @@ def __init__( self, value_type ) - self.dimension_type = dimension_type + self.physical_dimension = physical_dimension if data is not None: self.data = data value_type = grpc_data_property_read_only( "properties.value_type", from_protobuf=lookup_table_column_value_type_from_pb ) - dimension_type = grpc_data_property( + + # We renamed 'dimension_type' to 'physical_dimension' compared to the API, since + # 'dimension_type' could also be understood as the number of spatial dimensions. + physical_dimension = grpc_data_property( "properties.dimension_type", - from_protobuf=dimension_type_from_pb, - to_protobuf=dimension_type_to_pb, + from_protobuf=physical_dimension_from_pb, + to_protobuf=physical_dimension_to_pb, ) data: ReadWriteProperty[npt.NDArray[np.float64], npt.NDArray[np.float64]] = grpc_data_property( "properties.data", diff --git a/tests/unittests/test_lookup_table_column.py b/tests/unittests/test_lookup_table_column.py index fd4a5af4fc..ff895a37e9 100644 --- a/tests/unittests/test_lookup_table_column.py +++ b/tests/unittests/test_lookup_table_column.py @@ -26,10 +26,10 @@ import pytest from ansys.acp.core import ( - DimensionType, LookUpTable1DColumn, LookUpTable3DColumn, LookUpTableColumnValueType, + PhysicalDimension, ) from .common.tree_object_tester import ( @@ -115,7 +115,7 @@ class TestLookUpTableColumn(WithLockedMixin, TreeObjectTester): def default_properties(default_data): return { "value_type": LookUpTableColumnValueType.SCALAR, - "dimension_type": DimensionType.DIMENSIONLESS, + "physical_dimension": PhysicalDimension.DIMENSIONLESS, "data": default_data, } @@ -128,9 +128,9 @@ def object_properties(column_data, column_value_type): read_write=[ ("name", "some_name"), ("data", column_data), - ("dimension_type", DimensionType.TIME), - ("dimension_type", DimensionType.CURRENCY), - ("dimension_type", DimensionType.MASS), + ("physical_dimension", PhysicalDimension.TIME), + ("physical_dimension", PhysicalDimension.CURRENCY), + ("physical_dimension", PhysicalDimension.MASS), ], read_only=[ ("id", "some_id"), @@ -141,7 +141,7 @@ def object_properties(column_data, column_value_type): { "name": "some_name", "data": column_data, - "dimension_type": DimensionType.TIME, + "physical_dimension": PhysicalDimension.TIME, "value_type": column_value_type, } ], From 3ae9d16ec360754137bd5faeff3d7ed62b6a0e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Thu, 28 Nov 2024 21:38:35 +0100 Subject: [PATCH 89/96] Sort examples (#719) * Sort examples --- doc/create_doc_windows.ps1 | 4 ++-- ...ettes-ply-directions.py => 002-rosettes-ply-directions.py} | 0 ...imple-selection-rules.py => 003-simple-selection-rules.py} | 0 ...ced-selection-rules.py => 004-advanced-selection-rules.py} | 0 ...tion-lookup-table.py => 005-ply-direction-lookup-table.py} | 0 ...ness-definitions.py => 006-layup-thickness-definitions.py} | 0 ...01-sandwich-panel-layup.py => 010-sandwich-panel-layup.py} | 2 +- .../{07-imported-plies.py => 030-imported-plies.py} | 0 ...21-imported-solid-model.py => 031-imported-solid-model.py} | 0 9 files changed, 3 insertions(+), 3 deletions(-) rename examples/modeling_features/{05-rosettes-ply-directions.py => 002-rosettes-ply-directions.py} (100%) rename examples/modeling_features/{02-simple-selection-rules.py => 003-simple-selection-rules.py} (100%) rename examples/modeling_features/{03-advanced-selection-rules.py => 004-advanced-selection-rules.py} (100%) rename examples/modeling_features/{06-ply-direction-lookup-table.py => 005-ply-direction-lookup-table.py} (100%) rename examples/modeling_features/{04-layup-thickness-definitions.py => 006-layup-thickness-definitions.py} (100%) rename examples/modeling_features/{01-sandwich-panel-layup.py => 010-sandwich-panel-layup.py} (99%) rename examples/modeling_features/{07-imported-plies.py => 030-imported-plies.py} (100%) rename examples/modeling_features/{021-imported-solid-model.py => 031-imported-solid-model.py} (100%) diff --git a/doc/create_doc_windows.ps1 b/doc/create_doc_windows.ps1 index 8ba39b514f..086264b279 100644 --- a/doc/create_doc_windows.ps1 +++ b/doc/create_doc_windows.ps1 @@ -18,9 +18,9 @@ $Env:PYMAPDL_START_INSTANCE="FALSE" $Env:PYDPF_COMPOSITES_DOCKER_CONTAINER_PORT=59992 $Env:SPHINXOPT_NITPICKY=0 # whether to skip the gallery (examples) -$Env:PYACP_DOC_SKIP_GALLERY=0 +$Env:PYACP_DOC_SKIP_GALLERY="0" # whether to skip the API documentation -$Env:PYACP_DOC_SKIP_API=0 +$Env:PYACP_DOC_SKIP_API="0" $ParentDir = Split-Path -Parent $PSScriptRoot $DockerComposeFile = Join-Path -Path $ParentDir -ChildPath "docker-compose/docker-compose-extras.yaml" diff --git a/examples/modeling_features/05-rosettes-ply-directions.py b/examples/modeling_features/002-rosettes-ply-directions.py similarity index 100% rename from examples/modeling_features/05-rosettes-ply-directions.py rename to examples/modeling_features/002-rosettes-ply-directions.py diff --git a/examples/modeling_features/02-simple-selection-rules.py b/examples/modeling_features/003-simple-selection-rules.py similarity index 100% rename from examples/modeling_features/02-simple-selection-rules.py rename to examples/modeling_features/003-simple-selection-rules.py diff --git a/examples/modeling_features/03-advanced-selection-rules.py b/examples/modeling_features/004-advanced-selection-rules.py similarity index 100% rename from examples/modeling_features/03-advanced-selection-rules.py rename to examples/modeling_features/004-advanced-selection-rules.py diff --git a/examples/modeling_features/06-ply-direction-lookup-table.py b/examples/modeling_features/005-ply-direction-lookup-table.py similarity index 100% rename from examples/modeling_features/06-ply-direction-lookup-table.py rename to examples/modeling_features/005-ply-direction-lookup-table.py diff --git a/examples/modeling_features/04-layup-thickness-definitions.py b/examples/modeling_features/006-layup-thickness-definitions.py similarity index 100% rename from examples/modeling_features/04-layup-thickness-definitions.py rename to examples/modeling_features/006-layup-thickness-definitions.py diff --git a/examples/modeling_features/01-sandwich-panel-layup.py b/examples/modeling_features/010-sandwich-panel-layup.py similarity index 99% rename from examples/modeling_features/01-sandwich-panel-layup.py rename to examples/modeling_features/010-sandwich-panel-layup.py index b2d37ea992..b87a8f39ab 100644 --- a/examples/modeling_features/01-sandwich-panel-layup.py +++ b/examples/modeling_features/010-sandwich-panel-layup.py @@ -21,7 +21,7 @@ # SOFTWARE. """ -.. _sandwich_panel: +.. _sandwich_panel_example: Sandwich panel ============== diff --git a/examples/modeling_features/07-imported-plies.py b/examples/modeling_features/030-imported-plies.py similarity index 100% rename from examples/modeling_features/07-imported-plies.py rename to examples/modeling_features/030-imported-plies.py diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/031-imported-solid-model.py similarity index 100% rename from examples/modeling_features/021-imported-solid-model.py rename to examples/modeling_features/031-imported-solid-model.py From ec6d1530b8bd3f6d00340925336c15eb4f9ea016 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Fri, 29 Nov 2024 13:35:39 +0100 Subject: [PATCH 90/96] Fix: disable 'price' on 'ModelElementalData' (#721) Comment out the `price` attribute on the `ModelElementalData`, as a workaround to #717. Add a mass plot to the imported solid model example. Closes #717. Follow-up tasks tracked by #720. --- examples/modeling_features/031-imported-solid-model.py | 8 +++++++- src/ansys/acp/core/_tree_objects/model.py | 8 +++++++- tests/unittests/test_model.py | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/modeling_features/031-imported-solid-model.py b/examples/modeling_features/031-imported-solid-model.py index c14cd7c86f..5b4ac76d17 100644 --- a/examples/modeling_features/031-imported-solid-model.py +++ b/examples/modeling_features/031-imported-solid-model.py @@ -58,7 +58,7 @@ from ansys.acp.core import ElementTechnology, LayupMappingRosetteSelectionMethod, launch_acp from ansys.acp.core.extras import ExampleKeys, get_example_file -# sphinx_gallery_thumbnail_number = 4 +# sphinx_gallery_thumbnail_number = 5 # %% @@ -125,6 +125,12 @@ model.update() model.solid_mesh.to_pyvista().plot(show_edges=True) +# %% +# Show the mass of the solid model elements +mass_data = model.elemental_data.mass +assert mass_data is not None +mass_data.get_pyvista_mesh(mesh=model.solid_mesh).plot(show_edges=True) + # %% # Add other mapping objects imported_solid_model.create_layup_mapping_object( diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index c11ba8a4a4..3c1adbcc70 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -187,7 +187,13 @@ class ModelElementalData(ElementalData): thickness: ScalarData[np.float64] | None = None relative_thickness_correction: ScalarData[np.float64] | None = None area: ScalarData[np.float64] | None = None - price: ScalarData[np.float64] | None = None + # Retrieving the 'price' can crash the server if the model contains void + # analysis plies (on an imported solid model). + # This is fixed in the backend for 2025R2, but for now we simply comment + # out the property. In this way, the other properties can still be accessed, + # and we can avoid the crash. + # See https://github.com/ansys/pyacp/issues/717 + # price: ScalarData[np.float64] | None = None volume: ScalarData[np.float64] | None = None mass: ScalarData[np.float64] | None = None offset: ScalarData[np.float64] | None = None diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 827cd781da..29eeb7469e 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -178,7 +178,7 @@ def test_elemental_data(minimal_complete_model): numpy.testing.assert_allclose(data.thickness.values, np.array([1e-4])) numpy.testing.assert_allclose(data.relative_thickness_correction.values, np.array([1.0])) numpy.testing.assert_allclose(data.area.values, np.array([9e4])) - numpy.testing.assert_allclose(data.price.values, np.array([0.0])) + # numpy.testing.assert_allclose(data.price.values, np.array([0.0])) # disabled due to issue #717. numpy.testing.assert_allclose(data.volume.values, np.array([9.0])) numpy.testing.assert_allclose(data.mass.values, np.array([7.065e-08])) numpy.testing.assert_allclose(data.offset.values, np.array([5e-5])) From 7d2e00a7ee4dd73293b63f2ceaae8f00c6d2667c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= <105842014+roosre@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:58:12 +0100 Subject: [PATCH 91/96] add sensor example (#723) * add sensor example --- doc/source/index.rst | 1 + examples/modeling_features/007-sensor.py | 173 +++++++++++++++++++ src/ansys/acp/core/extras/__init__.py | 2 + src/ansys/acp/core/extras/example_helpers.py | 15 +- 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 examples/modeling_features/007-sensor.py diff --git a/doc/source/index.rst b/doc/source/index.rst index b8445650dc..60ec2f7190 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -100,3 +100,4 @@ Limitations * Section cuts cannot be visualized. * Sampling point analysis data is not available. * Imported solid model mapping statistics are not available. +* Sensor by solid model is not yet supported. diff --git a/examples/modeling_features/007-sensor.py b/examples/modeling_features/007-sensor.py new file mode 100644 index 0000000000..7a4add21e7 --- /dev/null +++ b/examples/modeling_features/007-sensor.py @@ -0,0 +1,173 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _sensor_example: + +Sensor +====== + +The :class:`.Sensor` capabilities to analyze the composite structure +is demonstrated in this example. A sensor is used to compute the weight, +area, cost, etc. of the model or specific entities such as ply material, +modeling ply, etc. +""" + +# %% +# Import modules +# -------------- +# +# Import the standard library and third-party dependencies. +import pathlib +import tempfile + +import pyvista + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import SensorType, UnitSystemType, launch_acp +from ansys.acp.core.extras import RACE_CARE_NOSE_CAMERA_METER, ExampleKeys, get_example_file + +# sphinx_gallery_thumbnail_number = 2 + + +# %% +# Start ACP and load the model +# ---------------------------- + +# %% +# Get the example file from the server. +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +acph5_input_file = get_example_file(ExampleKeys.RACE_CAR_NOSE_ACPH5, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Load the model from the input file which contains +# a formula 1 front wing with layup. The plot shows the total +# laminate thickness per element. +model = acp.import_model(acph5_input_file) +model.unit_system = UnitSystemType.SI +print(model.unit_system) +model.update() + +thickness_data = model.elemental_data.thickness +if thickness_data is not None: + plotter = pyvista.Plotter() + plotter.add_mesh(thickness_data.get_pyvista_mesh(model.mesh), show_edges=False) + plotter.camera_position = RACE_CARE_NOSE_CAMERA_METER + plotter.show() + +# %% +# Set price per area for all fabrics. +model.fabrics["UD"].area_price = 15 # $/m^2 +model.fabrics["woven"].area_price = 23 # $/m^2 +model.fabrics["core_4mm"].area_price = 7 # $/m^2 + +# %% +# Sensor by area +# -------------- + +# %% +# Entire Model +# ~~~~~~~~~~~~ +# The first sensor is applied to the entire model to compute for example +# the total weight, area of production material, and material cost. +sensor_by_area = model.create_sensor( + name="By Area", + sensor_type=SensorType.SENSOR_BY_AREA, + entities=[model.element_sets["All_Elements"]], +) +# %% +# Update the model to compute the sensor values. +model.update() + + +def print_measures(my_sensor): + print(f"Price: {my_sensor.price:.2f} $") + print(f"Weight: {my_sensor.weight:.2f} kg") + print(f"Covered area: {my_sensor.covered_area:.2f} m²") + print(f"Production ply area: {my_sensor.production_ply_area:.2f} m²") + cog = my_sensor.center_of_gravity + print(f"Center of gravity: ({cog[0]:.2f}, {cog[1]:.2f}, {cog[2]:.2f}) m") + + +# %% +# Print the values. The ``production ply area`` is the area of production material. +print_measures(sensor_by_area) + +# %% +# Scope to a specific component +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Compute the measures for the nose only. Note that :class:`.OrientedSelectionSet` +# can also be used to scope the sensor. +eset_nose = model.element_sets["els_nose"] +sensor_by_area.entities = [eset_nose] +model.update() +print_measures(sensor_by_area) +plotter = pyvista.Plotter() +plotter.add_mesh(eset_nose.mesh.to_pyvista(), show_edges=False, opacity=1, color="turquoise") +plotter.add_mesh(model.mesh.to_pyvista(), show_edges=False, opacity=0.2) +plotter.camera_position = RACE_CARE_NOSE_CAMERA_METER +plotter.show() + +# %% +# Sensor by material +# ------------------ +# +# A sensor can also be used to compute the amount of a certain ply material +# (:class:`.Fabric`, :class:`.Stackup`, :class:`.SubLaminate`). +sensor_by_material = model.create_sensor( + name="By Material", + sensor_type=SensorType.SENSOR_BY_MATERIAL, + entities=[model.fabrics["UD"]], +) +print_measures(sensor_by_area) + +# %% +# Sensor by ply +# ------------- +# +# A sensor can also be scoped to a specific ply or a list of plies. In this example, +# a ply of the suction side and a ply of the pressure side of wing 3 are selected. +mg = model.modeling_groups["wing_3"] +modeling_plies = [ + mg.modeling_plies["mp.wing_3.1_suction"], + mg.modeling_plies["mp.wing_3.1_pressure.2"], +] +sensor_by_ply = model.create_sensor( + name="By Ply", + sensor_type=SensorType.SENSOR_BY_PLIES, + entities=modeling_plies, +) +model.update() +print_measures(sensor_by_ply) +plotter = pyvista.Plotter() +for ply in modeling_plies: + plotter.add_mesh(ply.mesh.to_pyvista(), show_edges=False, opacity=1, color="turquoise") +plotter.add_mesh(model.mesh.to_pyvista(), show_edges=False, opacity=0.2) +plotter.camera_position = RACE_CARE_NOSE_CAMERA_METER +plotter.show() diff --git a/src/ansys/acp/core/extras/__init__.py b/src/ansys/acp/core/extras/__init__.py index 8d20f00f4f..c1e7427e45 100644 --- a/src/ansys/acp/core/extras/__init__.py +++ b/src/ansys/acp/core/extras/__init__.py @@ -24,6 +24,7 @@ from ansys.acp.core.extras.example_helpers import ( FLAT_PLATE_SHELL_CAMERA, FLAT_PLATE_SOLID_CAMERA, + RACE_CARE_NOSE_CAMERA_METER, ExampleKeys, get_example_file, ) @@ -33,4 +34,5 @@ "get_example_file", "FLAT_PLATE_SHELL_CAMERA", "FLAT_PLATE_SOLID_CAMERA", + "RACE_CARE_NOSE_CAMERA_METER", ] diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index 141b85a723..337b51db38 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -33,7 +33,13 @@ import urllib.parse import urllib.request -__all__ = ["ExampleKeys", "get_example_file"] +__all__ = [ + "ExampleKeys", + "get_example_file", + "FLAT_PLATE_SHELL_CAMERA", + "FLAT_PLATE_SOLID_CAMERA", + "RACE_CARE_NOSE_CAMERA_METER", +] from typing import TYPE_CHECKING @@ -58,6 +64,13 @@ (-0.2895, 0.9160, -0.2776), ] +# Order of inputs: position, rotation point, orientation +RACE_CARE_NOSE_CAMERA_METER = [ + (1.614, 1.154, 2.243), + (0.450, 0.238, -0.181), + (-0.1094, 0.9460, -0.3050), +] + @dataclasses.dataclass class _ExampleLocation: From 98ea99755a43632c8e7d747faf2e6cdbd7a3b13a Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Fri, 29 Nov 2024 14:51:24 +0100 Subject: [PATCH 92/96] Doc: remove beta note from the main doc page (#709) Remove the note that PyACP is in beta from the main documentation page. Switch the trove classifier to indicate that PyACP is stable. Closes #687. --- doc/source/index.rst | 6 ------ pyproject.toml | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 60ec2f7190..59f61b5a02 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -15,12 +15,6 @@ PyACP ----- -.. note:: - - PyACP is currently released as a beta version. This means that the API may - change in future releases. We encourage you to try PyACP and provide us with - feedback. - PyACP enables modelling continuous-fiber composite structures from within your Python environment. It provides access to the features of Ansys Composite PrepPost (ACP) through a Python interface. diff --git a/pyproject.toml b/pyproject.toml index 065247e3e5..34504885eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,12 +19,13 @@ include = ["src/**/docker-compose.yaml"] # Less than critical but helpful classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + "Topic :: Scientific/Engineering", ] [tool.poetry.urls] From b6314684630f77cc9f7923590fe336e2d925e0da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:16:54 +0100 Subject: [PATCH 93/96] Bump the dependencies group with 5 updates (#725) * Bump the dependencies group with 5 updates Bumps the dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | [pyvista](https://github.com/pyvista/pyvista) | `0.44.1` | `0.44.2` | | [ansys-dpf-core](https://github.com/ansys/pydpf-core) | `0.13.2` | `0.13.3` | | [matplotlib](https://github.com/matplotlib/matplotlib) | `3.9.2` | `3.9.3` | | [pytest](https://github.com/pytest-dev/pytest) | `8.3.3` | `8.3.4` | | [hypothesis](https://github.com/HypothesisWorks/hypothesis) | `6.119.4` | `6.122.1` | Updates `pyvista` from 0.44.1 to 0.44.2 - [Release notes](https://github.com/pyvista/pyvista/releases) - [Commits](https://github.com/pyvista/pyvista/compare/v0.44.1...v0.44.2) Updates `ansys-dpf-core` from 0.13.2 to 0.13.3 - [Release notes](https://github.com/ansys/pydpf-core/releases) - [Commits](https://github.com/ansys/pydpf-core/compare/v0.13.2...v0.13.3) Updates `matplotlib` from 3.9.2 to 3.9.3 - [Release notes](https://github.com/matplotlib/matplotlib/releases) - [Commits](https://github.com/matplotlib/matplotlib/compare/v3.9.2...v3.9.3) Updates `pytest` from 8.3.3 to 8.3.4 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.3...8.3.4) Updates `hypothesis` from 6.119.4 to 6.122.1 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.119.4...hypothesis-python-6.122.1) --- updated-dependencies: - dependency-name: pyvista dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: ansys-dpf-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: matplotlib dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: hypothesis dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] * Fix vale warnings --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dominik Gresch --- .../{README.md => README.txt} | 2 - .../user_guide/howto/file_management.rst | 2 +- poetry.lock | 127 +++++++++--------- 3 files changed, 65 insertions(+), 66 deletions(-) rename doc/source/_static/gallery_thumbnails/{README.md => README.txt} (97%) diff --git a/doc/source/_static/gallery_thumbnails/README.md b/doc/source/_static/gallery_thumbnails/README.txt similarity index 97% rename from doc/source/_static/gallery_thumbnails/README.md rename to doc/source/_static/gallery_thumbnails/README.txt index ce898610c3..ef35fe7775 100644 --- a/doc/source/_static/gallery_thumbnails/README.md +++ b/doc/source/_static/gallery_thumbnails/README.txt @@ -1,5 +1,3 @@ - - This directory contains thumbnails for the gallery examples which are not built in CI. The thumbnails are used in the gallery index page. diff --git a/doc/source/user_guide/howto/file_management.rst b/doc/source/user_guide/howto/file_management.rst index fb8f06c0f8..fcb8e3d004 100644 --- a/doc/source/user_guide/howto/file_management.rst +++ b/doc/source/user_guide/howto/file_management.rst @@ -55,7 +55,7 @@ handles the upload automatically. Loading input files ~~~~~~~~~~~~~~~~~~~ -To load an input file, pass the file path on your local machine to the +To load an input file, pass its path on your local machine to the :meth:`.import_model` method: .. doctest:: diff --git a/poetry.lock b/poetry.lock index a49df5874e..b45a84b4cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -253,22 +253,22 @@ test = ["pytest (>=7.1.2)", "pytest-cov (>=3.0.0)", "pytest-rerunfailures (>=11. [[package]] name = "ansys-dpf-core" -version = "0.13.2" +version = "0.13.3" description = "Data Processing Framework - Python Core" optional = true python-versions = "<4,>=3.9" files = [ - {file = "ansys_dpf_core-0.13.2-py3-none-any.whl", hash = "sha256:91f6d23d14dbc3485f6141747ba2e3d98212a193b4f681fc587f46497162bb4e"}, - {file = "ansys_dpf_core-0.13.2-py3-none-manylinux1_x86_64.whl", hash = "sha256:654493f4c1b749f85b306bc62492dbf10beaaecc1a6fd3edb8b9168111768422"}, - {file = "ansys_dpf_core-0.13.2-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:38ce4df8a62a811b7d1eac5f8351a6f3df8b6655a4b639d160cb42f427099c95"}, - {file = "ansys_dpf_core-0.13.2-py3-none-win_amd64.whl", hash = "sha256:390c1a0bbad727e29e93f87549105480cdf91c05ed1e025b17709f315c607f87"}, + {file = "ansys_dpf_core-0.13.3-py3-none-any.whl", hash = "sha256:09d1089005043728b8ac53dd0c88dee9c27b3a3dc051ab586bc658bf403ab0e2"}, + {file = "ansys_dpf_core-0.13.3-py3-none-manylinux1_x86_64.whl", hash = "sha256:ff76d86892eb34a6f8ac97b26e50aefc7456c0b9535a72b71a2d05d1897a4065"}, + {file = "ansys_dpf_core-0.13.3-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:bd1a6a80f8f56c8efb1bf67c3f621aaa5adaa45b427802fc2fbeb952c8ad4a47"}, + {file = "ansys_dpf_core-0.13.3-py3-none-win_amd64.whl", hash = "sha256:4daa55f8644e5745eff34eeecad23671957508bb5a2878b484b29ad0375677ce"}, ] [package.dependencies] google-api-python-client = "*" grpcio = ">=1.63.0" importlib-metadata = ">=4.0" -numpy = "<2" +numpy = "*" packaging = "*" protobuf = "*" psutil = "*" @@ -276,7 +276,7 @@ setuptools = "*" tqdm = "*" [package.extras] -plotting = ["imageio (<2.28.1)", "imageio-ffmpeg", "matplotlib (>=3.2)", "pyvista (>=0.32.0)"] +plotting = ["imageio (<2.28.1)", "imageio-ffmpeg", "matplotlib (>=3.2)", "pyvista (>=0.32.0)", "vtk (!=9.4.0)"] [[package]] name = "ansys-mapdl-core" @@ -1862,13 +1862,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "hypothesis" -version = "6.119.4" +version = "6.122.1" description = "A library for property-based testing" optional = false python-versions = ">=3.9" files = [ - {file = "hypothesis-6.119.4-py3-none-any.whl", hash = "sha256:333958da7855048850c3d2b6a929d44a3c89ca9eafcfddcacc3570140915eba5"}, - {file = "hypothesis-6.119.4.tar.gz", hash = "sha256:1a7d12709c0e96c1d85aca76d1594b34b5958623e00511592eba674acd4f3392"}, + {file = "hypothesis-6.122.1-py3-none-any.whl", hash = "sha256:59e52da0f2529b40f0b7bd0c3c61d8b3fe3337102800bf3534c53d4a8bdf8a6d"}, + {file = "hypothesis-6.122.1.tar.gz", hash = "sha256:23280e802eef88316b02cb32205d74b5bf2e3de4a378e2579a8974117c512b83"}, ] [package.dependencies] @@ -1877,10 +1877,10 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.77)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.18)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.19.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] +all = ["black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.78)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.18)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.19.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] -crosshair = ["crosshair-tool (>=0.0.77)", "hypothesis-crosshair (>=0.0.18)"] +crosshair = ["crosshair-tool (>=0.0.78)", "hypothesis-crosshair (>=0.0.18)"] dateutil = ["python-dateutil (>=1.4)"] django = ["django (>=4.2)"] dpcontracts = ["dpcontracts (>=0.4)"] @@ -2536,51 +2536,52 @@ files = [ [[package]] name = "matplotlib" -version = "3.9.2" +version = "3.9.3" description = "Python plotting package" optional = true python-versions = ">=3.9" files = [ - {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, - {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, - {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, - {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, - {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, - {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, - {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, - {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, - {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, - {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, - {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, - {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, - {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, - {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, - {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, - {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, - {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, - {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, - {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, - {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, - {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, - {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, - {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, - {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, - {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, - {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, - {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, - {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, - {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, - {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, - {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, - {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, - {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, - {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, - {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, - {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, + {file = "matplotlib-3.9.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:41b016e3be4e740b66c79a031a0a6e145728dbc248142e751e8dab4f3188ca1d"}, + {file = "matplotlib-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e0143975fc2a6d7136c97e19c637321288371e8f09cff2564ecd73e865ea0b9"}, + {file = "matplotlib-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f459c8ee2c086455744723628264e43c884be0c7d7b45d84b8cd981310b4815"}, + {file = "matplotlib-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687df7ceff57b8f070d02b4db66f75566370e7ae182a0782b6d3d21b0d6917dc"}, + {file = "matplotlib-3.9.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:edd14cf733fdc4f6e6fe3f705af97676a7e52859bf0044aa2c84e55be739241c"}, + {file = "matplotlib-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c40c244221a1adbb1256692b1133c6fb89418df27bf759a31a333e7912a4010"}, + {file = "matplotlib-3.9.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cf2a60daf6cecff6828bc608df00dbc794380e7234d2411c0ec612811f01969d"}, + {file = "matplotlib-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:213d6dc25ce686516208d8a3e91120c6a4fdae4a3e06b8505ced5b716b50cc04"}, + {file = "matplotlib-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c52f48eb75fcc119a4fdb68ba83eb5f71656999420375df7c94cc68e0e14686e"}, + {file = "matplotlib-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c93796b44fa111049b88a24105e947f03c01966b5c0cc782e2ee3887b790a3"}, + {file = "matplotlib-3.9.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cd1077b9a09b16d8c3c7075a8add5ffbfe6a69156a57e290c800ed4d435bef1d"}, + {file = "matplotlib-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:c96eeeb8c68b662c7747f91a385688d4b449687d29b691eff7068a4602fe6dc4"}, + {file = "matplotlib-3.9.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a361bd5583bf0bcc08841df3c10269617ee2a36b99ac39d455a767da908bbbc"}, + {file = "matplotlib-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e14485bb1b83eeb3d55b6878f9560240981e7bbc7a8d4e1e8c38b9bd6ec8d2de"}, + {file = "matplotlib-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a8d279f78844aad213c4935c18f8292a9432d51af2d88bca99072c903948045"}, + {file = "matplotlib-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6c12514329ac0d03128cf1dcceb335f4fbf7c11da98bca68dca8dcb983153a9"}, + {file = "matplotlib-3.9.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6e9de2b390d253a508dd497e9b5579f3a851f208763ed67fdca5dc0c3ea6849c"}, + {file = "matplotlib-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d796272408f8567ff7eaa00eb2856b3a00524490e47ad505b0b4ca6bb8a7411f"}, + {file = "matplotlib-3.9.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:203d18df84f5288973b2d56de63d4678cc748250026ca9e1ad8f8a0fd8a75d83"}, + {file = "matplotlib-3.9.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b651b0d3642991259109dc0351fc33ad44c624801367bb8307be9bfc35e427ad"}, + {file = "matplotlib-3.9.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66d7b171fecf96940ce069923a08ba3df33ef542de82c2ff4fe8caa8346fa95a"}, + {file = "matplotlib-3.9.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be0ba61f6ff2e6b68e4270fb63b6813c9e7dec3d15fc3a93f47480444fd72f0"}, + {file = "matplotlib-3.9.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d6b2e8856dec3a6db1ae51aec85c82223e834b228c1d3228aede87eee2b34f9"}, + {file = "matplotlib-3.9.3-cp313-cp313-win_amd64.whl", hash = "sha256:90a85a004fefed9e583597478420bf904bb1a065b0b0ee5b9d8d31b04b0f3f70"}, + {file = "matplotlib-3.9.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3119b2f16de7f7b9212ba76d8fe6a0e9f90b27a1e04683cd89833a991682f639"}, + {file = "matplotlib-3.9.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87ad73763d93add1b6c1f9fcd33af662fd62ed70e620c52fcb79f3ac427cf3a6"}, + {file = "matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:026bdf3137ab6022c866efa4813b6bbeddc2ed4c9e7e02f0e323a7bca380dfa0"}, + {file = "matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760a5e89ebbb172989e8273024a1024b0f084510b9105261b3b00c15e9c9f006"}, + {file = "matplotlib-3.9.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a42b9dc42de2cfe357efa27d9c50c7833fc5ab9b2eb7252ccd5d5f836a84e1e4"}, + {file = "matplotlib-3.9.3-cp313-cp313t-win_amd64.whl", hash = "sha256:e0fcb7da73fbf67b5f4bdaa57d85bb585a4e913d4a10f3e15b32baea56a67f0a"}, + {file = "matplotlib-3.9.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:031b7f5b8e595cc07def77ec5b58464e9bb67dc5760be5d6f26d9da24892481d"}, + {file = "matplotlib-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9fa6e193c14d6944e0685cdb527cb6b38b0e4a518043e7212f214113af7391da"}, + {file = "matplotlib-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e6eefae6effa0c35bbbc18c25ee6e0b1da44d2359c3cd526eb0c9e703cf055d"}, + {file = "matplotlib-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d3e5c7a99bd28afb957e1ae661323b0800d75b419f24d041ed1cc5d844a764"}, + {file = "matplotlib-3.9.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:816a966d5d376bf24c92af8f379e78e67278833e4c7cbc9fa41872eec629a060"}, + {file = "matplotlib-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fb0b37c896172899a4a93d9442ffdc6f870165f59e05ce2e07c6fded1c15749"}, + {file = "matplotlib-3.9.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f2a4ea08e6876206d511365b0bc234edc813d90b930be72c3011bbd7898796f"}, + {file = "matplotlib-3.9.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b081dac96ab19c54fd8558fac17c9d2c9cb5cc4656e7ed3261ddc927ba3e2c5"}, + {file = "matplotlib-3.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a0a63cb8404d1d1f94968ef35738900038137dab8af836b6c21bb6f03d75465"}, + {file = "matplotlib-3.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:896774766fd6be4571a43bc2fcbcb1dcca0807e53cab4a5bf88c4aa861a08e12"}, + {file = "matplotlib-3.9.3.tar.gz", hash = "sha256:cd5dbbc8e25cad5f706845c4d100e2c8b34691b412b93717ce38d8ae803bcfa5"}, ] [package.dependencies] @@ -2595,7 +2596,7 @@ pyparsing = ">=2.3.1" python-dateutil = ">=2.7" [package.extras] -dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] [[package]] name = "matplotlib-inline" @@ -3687,13 +3688,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -3788,20 +3789,20 @@ files = [ [[package]] name = "pyvista" -version = "0.44.1" +version = "0.44.2" description = "Easier Pythonic interface to VTK" optional = true python-versions = ">=3.8" files = [ - {file = "pyvista-0.44.1-py3-none-any.whl", hash = "sha256:7a80e8114220ca36d57a4def8e6a3067c908b53b62aa426ea76c76069bb6d1c0"}, - {file = "pyvista-0.44.1.tar.gz", hash = "sha256:63976f5d57d151b3f7e1616dde40dcf56a66d1f37f6db067087fa9cc9667f512"}, + {file = "pyvista-0.44.2-py3-none-any.whl", hash = "sha256:8530d35f57cdaa33507ac9aec19e3292be0bc9026b28e4f12df7e8054438d028"}, + {file = "pyvista-0.44.2.tar.gz", hash = "sha256:db65943c3c1c9ba49fe16f5a25a5bc23b74bf1ea7a38aae4ef9c4dc5838ccf5e"}, ] [package.dependencies] ipywidgets = {version = "*", optional = true, markers = "extra == \"jupyter\""} jupyter-server-proxy = {version = "*", optional = true, markers = "extra == \"jupyter\""} matplotlib = ">=3.0.1" -nest-asyncio = {version = "*", optional = true, markers = "extra == \"jupyter\""} +nest_asyncio = {version = "*", optional = true, markers = "extra == \"jupyter\""} numpy = ">=1.21.0" pillow = "*" pooch = "*" @@ -3812,13 +3813,13 @@ trame-server = {version = ">=2.11.7", optional = true, markers = "extra == \"jup trame-vtk = {version = ">=2.5.8", optional = true, markers = "extra == \"jupyter\""} trame-vuetify = {version = ">=2.3.1", optional = true, markers = "extra == \"jupyter\""} typing-extensions = "*" -vtk = "*" +vtk = "<9.4.0" [package.extras] all = ["pyvista[colormaps,io,jupyter]"] colormaps = ["cmocean", "colorcet"] io = ["imageio", "meshio (>=5.2)"] -jupyter = ["ipywidgets", "jupyter-server-proxy", "nest-asyncio", "trame (>=2.5.2)", "trame-client (>=2.12.7)", "trame-server (>=2.11.7)", "trame-vtk (>=2.5.8)", "trame-vuetify (>=2.3.1)"] +jupyter = ["ipywidgets", "jupyter-server-proxy", "nest_asyncio", "trame (>=2.5.2)", "trame-client (>=2.12.7)", "trame-server (>=2.11.7)", "trame-vtk (>=2.5.8)", "trame-vuetify (>=2.3.1)"] [[package]] name = "pywin32" From 10f4b16ab8af1856f4473f7bb220c84be68937b8 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 2 Dec 2024 09:39:37 +0100 Subject: [PATCH 94/96] Fix flaky object permanence test (#726) Fix the flaky object deletion test, by using weak references to track if an object is still alive instead of comparing the IDs. Closes #481. --- tests/unittests/test_object_permanence.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/unittests/test_object_permanence.py b/tests/unittests/test_object_permanence.py index c09f6b1e02..707ce70517 100644 --- a/tests/unittests/test_object_permanence.py +++ b/tests/unittests/test_object_permanence.py @@ -21,6 +21,7 @@ # SOFTWARE. import gc +import weakref import pytest @@ -43,23 +44,23 @@ def test_object_identity(model): assert mg_0 is mg_1 is mg_2 -def test_object_identity_after_deletion(model): +def test_object_deletion(model): """Check that objects are deleted when no longer referenced.""" key = list(model.modeling_groups.keys())[0] - # Immediately consume the objects via 'id' to ensure no references - # are kept by the test infrastructure. - id1 = id(model.modeling_groups[key]) + # Check the object is deleted when no longer referenced, after + # garbage collection. + ref = weakref.ref(model.modeling_groups[key]) + assert ref() is not None gc.collect() - id2 = id(model.modeling_groups[key]) - assert id1 != id2 + assert ref() is None - # test the inverse: keep a reference alive explicitly + # Test the inverse: keep a reference alive explicitly. The object + # should not be deleted. _ = model.modeling_groups[key] - id1 = id(model.modeling_groups[key]) + ref = weakref.ref(model.modeling_groups[key]) gc.collect() - id2 = id(model.modeling_groups[key]) - assert id1 == id2 + assert ref() is not None def test_unstored(): From da9ed3ba7765286fa5e3dd9dada136b98eb0e594 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 2 Dec 2024 11:22:37 +0100 Subject: [PATCH 95/96] Add compatibility and upgrade section (#727) Add a section to the docs specifying which features were added in 2025R1. Add a section detailing how the API was changed from the beta version to the stable release. Closes #724. --- doc/source/user_guide/compatibility.rst | 118 ++++++++++++++++++ .../user_guide/howto/file_management.rst | 2 + doc/source/user_guide/index.rst | 1 + .../config/vocabularies/ANSYS/accept.txt | 1 + 4 files changed, 122 insertions(+) create mode 100644 doc/source/user_guide/compatibility.rst diff --git a/doc/source/user_guide/compatibility.rst b/doc/source/user_guide/compatibility.rst new file mode 100644 index 0000000000..f9558757a0 --- /dev/null +++ b/doc/source/user_guide/compatibility.rst @@ -0,0 +1,118 @@ +Compatibility +============= + +Server version compatibility +---------------------------- + +PyACP is compatible with all versions of the ACP gRPC server since version 2024R2. + +However, some features are not available when using older versions of the server. +Version 2025R1 is the first full release of the ACP gRPC server, which makes +almost all features of ACP available through PyACP. + +Added in 2025R1 +~~~~~~~~~~~~~~~ + +The following features were added in version 2025R1 of the ACP gRPC server. + +Tree objects +'''''''''''' + +- :class:`.ButtJointSequence` +- :class:`.CutOffGeometry` +- :class:`.ExtrusionGuide` +- :class:`.FieldDefinition` +- :class:`.ImportedAnalysisPly` +- :class:`.ImportedModelingPly` +- :class:`.ImportedProductionPly` +- :class:`.ImportedSolidModel` +- :class:`.InterfaceLayer` +- :class:`.LayupMappingObject` +- :class:`.SamplingPoint` +- :class:`.SectionCut` +- :class:`.SnapToGeometry` +- :class:`.SolidElementSet` +- :class:`.SolidModel` + +Methods +''''''' + +- :meth:`.Model.import_hdf5_composite_cae` +- :meth:`.Model.export_hdf5_composite_cae` +- :meth:`.Model.import_materials` +- :meth:`.Model.export_modeling_ply_geometries` + +Other features +'''''''''''''' + +- Mesh attributes for classes other than the :class:`.Model` class. +- The ``.shell_mesh`` and ``.solid_mesh`` attributes. + + +Upgrading PyACP +--------------- + +The following section describes how to upgrade to newer versions of PyACP. + +Upgrading from the beta version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The beta version of PyACP did not yet provide a stable API. Consequently, some +backwards-incompatible changes were made in the first stable release to improve +the API. + +If you encounter any difficulties upgrading from the beta version, feel free to +open an `issue `_ on the PyACP GitHub +repository. + +Removed features +'''''''''''''''' + +- The ``ACPWorkflow`` class for managing file up- and download was removed. Instead, + file up- and download is now managed automatically by default. You can directly + use the :meth:`.ACPInstance.import_model` method for importing models, and methods + such as :meth:`.Model.save`, :meth:`.Model.export_analysis_model`, or + :meth:`.Model.export_hdf5_composite_cae` for saving / exporting data. + See the :ref:`file management section ` for more information. +- The ``get_composite_post_processing_files`` function was removed, since it only + covered the shell workflow. Instead, you can directly use the ``ansys.dpf.composites`` + API, as shown in the :ref:`workflow examples `. + +New submodules +'''''''''''''' + +Some features were moved into submodules instead of being exposed at the top level +``ansys.acp.core`` module: + +- Elemental, nodal, and mesh data types were moved to the ``ansys.acp.core.mesh_data`` submodule. +- The ``example_helpers`` submodule was moved to the ``ansys.acp.core.extras`` submodule. +- The ``get_dpf_unit_system`` function was moved to the ``ansys.acp.core.dpf_integration_helpers`` submodule. + + +Renamed classes +''''''''''''''' + +The following classes were renamed: + +- ``ACP`` renamed to ``ACPInstance``. +- ``DrapingMaterialType`` renamed to ``DrapingMaterialModel``. +- ``StatusType`` renamed to ``Status``. +- ``DimensionType`` renamed to ``PhysicalDimension``. +- ``CutoffMaterialType`` renamed to ``CutOffMaterialType``. +- ``CutoffRuleType`` renamed to ``CutOffRuleType``. +- ``CutoffSelectionRule`` renamed to ``CutOffSelectionRule``. +- ``CutoffSelectionRuleElementalData`` renamed to ``CutOffSelectionRuleElementalData`` and moved to ``ansys.acp.core.mesh_data``. +- ``CutoffSelectionRuleNodalData`` renamed to ``CutOffSelectionRuleNodalData`` and moved to ``ansys.acp.core.mesh_data``. +- ``PlyCutoffType`` renamed to ``PlyCutOffType``. +- ``DropoffMaterialType`` renamed to ``DropOffMaterialType``. + + +Renamed attributes +'''''''''''''''''' + +The following attributes were renamed: + +- ``dimension_type`` renamed to ``physical_dimension`` on the ``LookUpTable1DColumn`` and ``LookUpTable3DColumn`` classes. +- ``draping_type`` renamed to ``draping`` on the ``ModelingPly`` class. +- ``include_rule_type`` renamed to ``include_rule`` on all selection rule classes. +- ``relative_rule_type`` renamed to ``relative_rule`` on all selection rule classes. diff --git a/doc/source/user_guide/howto/file_management.rst b/doc/source/user_guide/howto/file_management.rst index fcb8e3d004..c4c785e4da 100644 --- a/doc/source/user_guide/howto/file_management.rst +++ b/doc/source/user_guide/howto/file_management.rst @@ -1,3 +1,5 @@ +.. _file_management: + Manage input and output files ============================= diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index be8fec05bd..66b4701e6c 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -11,4 +11,5 @@ section provides in-depth explanations. howto/index concepts/index + compatibility security_considerations diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index 2d0ac40ac8..291d7aeeed 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -10,3 +10,4 @@ APDL API untrusted DPF +2025R1 From 8b9723d8d69ee209680d32bca552254cd46eed64 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 2 Dec 2024 11:35:04 +0100 Subject: [PATCH 96/96] Bump version to 0.1rc1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5e3f4dd327..21f128b98f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "ansys-acp-core" -version = "0.1b2" +version = "0.1rc1" description = "Python library for ACP - Ansys Composite PrepPost" readme = "README.rst"