diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 502e18931c..d28cf4bba6 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -270,6 +270,11 @@ jobs: - name: Build HTML run: | xvfb-run poetry run make -C doc html SPHINXOPTS="-W --keep-going -v" + env: + PYMAPDL_PORT: "50557" + PYMAPDL_START_INSTANCE: "FALSE" + PYDPF_COMPOSITES_DOCKER_CONTAINER_PORT: "50558" + - name: Stop and clean up MAPDL and DPF servers run: | diff --git a/README.rst b/README.rst index a6b51191f0..ace81475fe 100644 --- a/README.rst +++ b/README.rst @@ -153,19 +153,9 @@ You will need to follow these steps: poetry shell -.. TODO: If we add tox, add instructions on its use here. - -.. 6. Verify your development installation by running: - -.. .. code-block:: bash - -.. tox - Testing ^^^^^^^ -.. TODO: If we add tox, add instructions on its use here. - The PyACP test suite uses `pytest`_. You can run it with .. code-block:: bash @@ -210,16 +200,9 @@ need to create a commit without running the hooks, you can skip them with ``git Documentation ^^^^^^^^^^^^^ - -To build the documentation, DPF Composites and MAPDL servers need to be running: - -.. code-block:: bash - - docker-compose -f docker-compose/docker-compose-extras.yaml up -d - -In addition, the PyACP server needs to be configured via ``ansys-launcher``, see `Launching ACP `_ above. - -It can then be built using `Sphinx`_. +Before generating the documentation, configure the PyACP server via the +``ansys-launcher``, see `Launching ACP `_ above. +If Ansys is installed the examples can directly built with `Sphinx`_. On Linux & MacOS: @@ -235,6 +218,20 @@ On Windows: The generated HTML files can be viewed with the browser of your choice. +Alternatively you can build the documentation by starting a mapdl and pydpf-composites docker +container. First ensure that you have accepted the DPF Preview License Agreement by setting the +ANSYS_DPF_ACCEPT_LA environment variable to Y +(see `DPF Preview License Agreement `_ +). In addition the ANSYSLMD_LICENSE_FILE environment variable needs be set to a +valid license server (e.g 1055@mylicenseserver.com). Then start the docker containers with: + +.. code-block:: bash + + docker-compose -f docker-compose/docker-compose-extras.yaml up -d + +Then build the documentation with the `Sphinx`_ commands mentioned above. + + Distribution ^^^^^^^^^^^^ @@ -264,4 +261,3 @@ License .. _pre-commit: https://pre-commit.com/ .. _pytest: https://docs.pytest.org/en/stable/ .. _Sphinx: https://www.sphinx-doc.org/en/master/ -.. _tox: https://tox.wiki/ diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 8207b507bc..ee70c7f812 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -15,3 +15,4 @@ and attributes. linked_object_definitions material_property_sets other_types + workflow diff --git a/doc/source/api/tree_objects.rst b/doc/source/api/tree_objects.rst index 582e8e05d6..10038b4954 100644 --- a/doc/source/api/tree_objects.rst +++ b/doc/source/api/tree_objects.rst @@ -34,3 +34,5 @@ ACP objects ProductionPly AnalysisPly Sensor + ScalarData + VectorData diff --git a/doc/source/api/workflow.rst b/doc/source/api/workflow.rst new file mode 100644 index 0000000000..ab69c6134c --- /dev/null +++ b/doc/source/api/workflow.rst @@ -0,0 +1,16 @@ +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/examples/index.rst b/doc/source/examples/index.rst index ce87ee1b2e..c2bd70a316 100644 --- a/doc/source/examples/index.rst +++ b/doc/source/examples/index.rst @@ -6,6 +6,3 @@ Examples .. include:: gallery_examples/index.rst :start-line: 2 - -:doc:`./others/local_pyacp_executable` demonstrates running the -PyACP server from a local executable instead of in a Docker container diff --git a/doc/source/examples/others/local_pyacp_executable.rst b/doc/source/examples/others/local_pyacp_executable.rst deleted file mode 100644 index 56624283ce..0000000000 --- a/doc/source/examples/others/local_pyacp_executable.rst +++ /dev/null @@ -1,289 +0,0 @@ -:orphan: - -.. _ref_example_local_executable: - -Local PyACP executable ----------------------- - -This example demonstrates running the PyACP server from a local executable instead of in a -Docker container. The gRPC server of ACP is delivered with the unified installer. -It can be beneficial to work with the local executable if the data is already stored locally -or to reduce the latency between the server and client. - -In this example, PyACP is used to add a lay-up to a MAPDL model (CDB file) -which already contains the material properties, boundary conditions and solution settings. -The MAPDL model with lay-up definition is solved with pyMAPDL. -Finally, the result is post-processed with pyDPF Composites to run a composite failure analysis. - -The first step is to specify the license server and the version of the unified installer. -These environment variables are used within the Python script and by the gRPC servers -(ACP, MAPDL, and DPF) for the license checkout. - -.. code-block:: bash - - set ANSYS_VERSION=231 - set ANSYSLMD_LICENSE_FILE=1055@my_license_server - -Ensure that the required Python modules (ansys-acp-core, ansys-dpf-core, ansys-dpf-composites -and ansys-mapdl) are installed before launching Python. - -.. code-block:: python - - import os - import pathlib - import tempfile - import numpy as np - import grpc - - ANSYS_VERSION = os.environ["ANSYS_VERSION"] - AWP_ROOT_KEY = f"AWP_ROOT{ANSYS_VERSION}" - - acp_grpc_exe = os.path.join(os.environ[AWP_ROOT_KEY], "ACP", "acp_grpcserver.exe") - - # Launch gRPC server and PyACP client - import ansys.acp.core as pyacp - - pyacp_server = pyacp.launch_acp( - binary_path=acp_grpc_exe, - port=50555, - stdout_file="pyacp.log", - stderr_file="pyacp.err", - ) - pyacp.wait_for_server(pyacp_server, timeout=30) # ensure the server is running - pyacp_client = pyacp.Client(pyacp_server) - - """ - Modelling of The Composite Lay-up with PyACP - """ - - # Import the model from MAPDL input file (CDB) - # Note: the unit system is required for the post-processing - CDB_FILENAME = "class40.cdb" - model = pyacp_client.import_model( - path=CDB_FILENAME, format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA - ) - model - - # Configure the materials. - # The ply type is required to run the failure analysis with dpf composites - mat_corecell_81kg = model.materials["1"] - mat_corecell_81kg.name = "Core Cell 81kg" - mat_corecell_81kg.ply_type = "isotropic_homogeneous_core" - - mat_corecell_103kg = model.materials["2"] - mat_corecell_103kg.name = "Core Cell 103kg" - mat_corecell_103kg.ply_type = "isotropic_homogeneous_core" - - mat_eglass_ud = model.materials["3"] - mat_eglass_ud.name = "E-Glass (uni-directional)" - mat_eglass_ud.ply_type = "regular" - - # Create Fabrics - corecell_81kg_5mm = model.create_fabric( - name="Corecell 81kg", thickness=0.005, material=mat_corecell_81kg - ) - corecell_103kg_10mm = model.create_fabric( - name="Corecell 103kg", thickness=0.01, material=mat_corecell_103kg - ) - eglass_ud_02mm = model.create_fabric( - name="eglass UD", thickness=0.0002, material=mat_eglass_ud - ) - - # Specify rosettes (coordinate systems) - ros_deck = model.create_rosette(name="ros_deck", origin=(-5.9334, -0.0481, 1.693)) - ros_hull = model.create_rosette(name="ros_hull", origin=(-5.3711, -0.0506, -0.2551)) - ros_bulkhead = model.create_rosette( - name="ros_bulkhead", - origin=(-5.622, 0.0022, 0.0847), - dir1=(0.0, 1.0, 0.0), - dir2=(0.0, 0.0, 1.0), - ) - ros_keeltower = model.create_rosette( - name="ros_keeltower", origin=(-6.0699, -0.0502, 0.623), dir1=(0.0, 0.0, 1.0) - ) - - # Add Oriented Selection Sets - oss_deck = model.create_oriented_selection_set( - name="oss_deck", - orientation_point=(-5.3806, -0.0016, 1.6449), - orientation_direction=(0.0, 0.0, -1.0), - element_sets=[model.element_sets["DECK"]], - rosettes=[ros_deck], - ) - - oss_hull = model.create_oriented_selection_set( - name="oss_hull", - orientation_point=(-5.12, 0.1949, -0.2487), - orientation_direction=(0.0, 0.0, 1.0), - element_sets=[model.element_sets["HULL_ALL"]], - rosettes=[ros_hull], - ) - - oss_bulkhead = model.create_oriented_selection_set( - name="oss_bulkhead", - orientation_point=(-5.622, -0.0465, -0.094), - orientation_direction=(1.0, 0.0, 0.0), - element_sets=[model.element_sets["BULKHEAD_ALL"]], - rosettes=[ros_bulkhead], - ) - - esets = [ - model.element_sets["KEELTOWER_AFT"], - model.element_sets["KEELTOWER_FRONT"], - model.element_sets["KEELTOWER_PORT"], - model.element_sets["KEELTOWER_STB"], - ] - - oss_keeltower = model.create_oriented_selection_set( - name="oss_keeltower", - orientation_point=(-6.1019, 0.0001, 1.162), - orientation_direction=(-1.0, 0.0, 0.0), - element_sets=esets, - rosettes=[ros_keeltower], - ) - - - # Add plies to all parts - def add_ply(mg, name, ply_material, angle, oss): - return mg.create_modeling_ply( - name=name, - ply_material=ply_material, - oriented_selection_sets=oss, - ply_angle=angle, - number_of_layers=1, - global_ply_nr=0, # add at the end - ) - - - angles = [-90.0, -60.0, -45.0 - 30.0, 0.0, 0.0, 30.0, 45.0, 60.0, 90.0] - for mg_name in ["hull", "deck", "bulkhead"]: - mg = model.create_modeling_group(name=mg_name) - oss_list = [model.oriented_selection_sets["oss_" + mg_name]] - for angle in angles: - add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list) - add_ply(mg, "corecell_103kg_10mm", corecell_103kg_10mm, 0.0, oss_list) - for angle in angles: - add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list) - - mg = model.create_modeling_group(name="keeltower") - oss_list = [model.oriented_selection_sets["oss_keeltower"]] - for angle in angles: - add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list) - - add_ply(mg, "corecell_81kg_5mm", corecell_81kg_5mm, 0.0, oss_list) - - for angle in angles: - add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list) - - # Update the lay-up model - model.update() - - # Store ACP model and generate the output for MAPDL and the post-processing with DPF - os.mkdir("tmp") - WORKDIR = os.path.join(os.path.abspath("."), "tmp") - ACPH5_FILE = os.path.join(WORKDIR, "class40.acph5") - CDB_FILENAME_OUT = os.path.join(WORKDIR, "class40_analysis_model.cdb") - COMPOSITE_DEFINITIONS_H5 = os.path.join(WORKDIR, "ACPCompositeDefinitions.h5") - MATML_FILE = os.path.join(WORKDIR, "materials.xml") - - # Store ACP DB - model.save(ACPH5_FILE, save_cache=True) - - # Input files for MAPDL and DPF - model.save_analysis_model(CDB_FILENAME_OUT) - model.export_shell_composite_definitions(COMPOSITE_DEFINITIONS_H5) - model.export_materials(MATML_FILE) - - """ - Solve Model with Composite efinitions with pyMAPDL - """ - - # Launch MAPDL - from ansys.mapdl.core import launch_mapdl - - mapdl = launch_mapdl() - # Load the CDB file with the composite lay-up - mapdl.input(CDB_FILENAME_OUT) - - # Solve and show deformations - mapdl.allsel() - mapdl.slashsolu() - mapdl.solve() - - mapdl.post1() - mapdl.set("last") - mapdl.post_processing.plot_nodal_displacement(component="NORM") - -.. figure:: ./../../images/class40_grpc_irf.png - :width: 300pt - - Total deformations (``usum``) - -.. code-block:: python - - """ - Run Failure Analysis with DPF Composites - """ - - # Import post-processing module (ansys-dpf-composites) - from ansys.dpf.composites.failure_criteria import ( - CombinedFailureCriterion, - MaxStrainCriterion, - MaxStressCriterion, - CoreFailureCriterion, - ) - from ansys.dpf.composites.result_definition import ResultDefinition - from ansys.dpf.composites.server_helpers import load_composites_plugin - import ansys.dpf.core as dpf - - # Launch local gRPC server of dpf and connect to - dpf_server = dpf.start_local_server(ansys_path=os.environ[AWP_ROOT_KEY]) - base = dpf.BaseService(server=dpf_server, load_operators=False) - base.load_library("Ans.Dpf.EngineeringData.dll", "EngineeringData") - composites_plugin_path = os.path.join( - os.environ[AWP_ROOT_KEY], - "dpf", - "plugins", - "dpf_composites", - "composite_operators.dll", - ) - base.load_library(composites_plugin_path, "Composites") - - # Configure failure criteria - max_strain = MaxStrainCriterion() - max_stress = MaxStressCriterion() - core_failure = CoreFailureCriterion() - - cfc = CombinedFailureCriterion( - name="Combined Failure Criterion", - failure_criteria=[max_strain, max_stress, core_failure], - ) - - rstfile_path = os.path.join(mapdl.directory, f"{mapdl.jobname}.rst") - - rd = ResultDefinition( - name="combined failure criteria", - rst_files=[rstfile_path], - material_files=[MATML_FILE], - composite_definitions=[COMPOSITE_DEFINITIONS_H5], - combined_failure_criterion=cfc, - ) - - # Configure and run DPF failure operator - fc_op = dpf.Operator("composite::composite_failure_operator") - elements = list([int(v) for v in np.arange(1, 3996)]) - rd.element_scope = elements - fc_op.inputs.result_definition(rd.to_json()) - output_all_elements = fc_op.outputs.fields_containerMax() - - failure_value_index = 1 - failure_mode_index = 0 - - # Plot inverse reserve factors - irf_field = output_all_elements[failure_value_index] - irf_field.plot() - -.. figure:: ./../../images/class40_grpc_irf.png - :width: 300pt - - Maximum inverse reserve factor of each element diff --git a/doc/source/images/class40_grpc_irf.png b/doc/source/images/class40_grpc_irf.png deleted file mode 100644 index 3aaf2cd49c..0000000000 Binary files a/doc/source/images/class40_grpc_irf.png and /dev/null differ diff --git a/doc/source/images/class40_grpc_usum.png b/doc/source/images/class40_grpc_usum.png deleted file mode 100644 index 78682dbddc..0000000000 Binary files a/doc/source/images/class40_grpc_usum.png and /dev/null differ diff --git a/examples/001_basic_flat_plate.py b/examples/001_basic_flat_plate.py new file mode 100644 index 0000000000..b040b2fc13 --- /dev/null +++ b/examples/001_basic_flat_plate.py @@ -0,0 +1,237 @@ +""" +.. _basic_flat_plate: + +Basic PyACP Example +=================== + +Define a Composite Lay-up with PyACP, solve the resulting model with PyMAPDL, and run +a failure analysis with PyDPF-Composites. + +The starting point is a MAPDL CDB file which contains the mesh, material data and +the boundary conditions. This model is imported in PyACP to define the lay-up. +PyACP exports the resulting model for PyMAPDL. Once the results are available, +the RST file is loaded in PyDPF composites. The additional input files (material.xml +and ACPCompositeDefinitions.h5) can also be stored with PyACP and passed to PyDPF Composites. +""" + + +# %% +# Import standard library and third-party dependencies +import pathlib +import tempfile + +import pyvista + +# %% +# Import pyACP dependencies +from ansys.acp.core import ( + ACPWorkflow, + Client, + ExampleKeys, + get_composite_post_processing_files, + get_dpf_unit_system, + get_example_file, + launch_acp, + print_model, +) + +# TODO: Import from top-level when available +from ansys.acp.core._tree_objects.enums import PlyType +from ansys.acp.core._tree_objects.material.property_sets import ( + ConstantEngineeringConstants, + ConstantStrainLimits, +) + +# Note: It is important to import mapdl before dpf, otherwise the plot defaults are messed up +# https://github.com/ansys/pydpf-core/issues/1363 +from ansys.mapdl.core import launch_mapdl + +# %% +# Get example file from server +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_CDB, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +pyacp_server = launch_acp() +pyacp_server.wait(timeout=30) +pyacp_client = Client(pyacp_server) + +# %% +# Define the input file and instantiate an ACPWorkflow +# The ACPWorkflow class provides convenience methods which simplify the file handling. +# It automatically creates a model based on the input file. + +workflow = ACPWorkflow( + acp_client=pyacp_client, + cdb_file_path=input_file, + local_working_directory=WORKING_DIR, +) + +model = workflow.model +print(workflow.working_directory.path) +print(model.unit_system) + +# %% +# Visualize the loaded mesh +mesh = model.mesh.to_pyvista() +mesh.plot(show_edges=True) + + +# %% +# Create an orthotropic material and fabric including strain limits, which are later +# used to post-process the simulation. +engineering_constants = ConstantEngineeringConstants( + 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 = ConstantStrainLimits( + 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=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 an oriented selection set and plot the orientations +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 = pyvista.Plotter() +plotter.add_mesh(model.mesh.to_pyvista(), color="white") +plotter.add_mesh( + oss.elemental_data.orientation.get_pyvista_glyphs(mesh=model.mesh, factor=0.01), + color="blue", +) +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"] + +plotter = pyvista.Plotter() +plotter.add_mesh(model.mesh.to_pyvista(), color="white", show_edges=True) +plotter.add_mesh( + modeling_ply.elemental_data.fiber_direction.get_pyvista_glyphs(mesh=model.mesh, factor=0.0008), +) +plotter.show() + +# %% +# Print the model tree for a quick overview +print_model(model) + +# %% +# Solve the model with MAPDL +# -------------------------- +# +# 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() + +# %% +# Post-processing: show displacements +mapdl.post1() +mapdl.set("last") +mapdl.post_processing.plot_nodal_displacement(component="NORM") + +# %% +# 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)) + +# %% +# Post-Processing with DPF composites +# ----------------------------------- +# +# Setup: configure imports and connect to the pyDPF Composites server +# and load the dpf composites plugin + +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 + +# %% +# Connect to the server. The ``connect_to_or_start_server`` function +# automatically loads the composites plugin. +dpf_server = connect_to_or_start_server() + +# %% +# Specify the Combined Failure Criterion +max_strain = MaxStrainCriterion() + +cfc = CombinedFailureCriterion( + name="Combined Failure Criterion", + failure_criteria=[max_strain], +) + +# %% +# Create the CompositeModel and configure its input +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, +) + +# %% +# Evaluate the failure criteria and plot it +output_all_elements = composite_model.evaluate_failure_criteria(cfc) +irf_field = output_all_elements.get_field({"failure_label": FailureOutput.FAILURE_VALUE}) +irf_field.plot() + +# %% +# Release composite model to close open streams to result file. +composite_model = None # type: ignore diff --git a/examples/002_start_from_existing_project.py b/examples/002_start_from_existing_project.py new file mode 100644 index 0000000000..d4109eb53c --- /dev/null +++ b/examples/002_start_from_existing_project.py @@ -0,0 +1,35 @@ +""" +.. _start from existing acph5 project: + +Start from existing acph5 project +================================= + + TODO: Write descriptions +""" + +import pathlib +import tempfile + +import ansys.acp.core as pyacp +from ansys.acp.core import ACPWorkflow, ExampleKeys, get_example_file, print_model + +# %% +# Get example file from server +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +input_cdb_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_CDB, WORKING_DIR) +input_acph5_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_ACPH5, WORKING_DIR) + +# Launch the PyACP server and connect to it. +pyacp_server = pyacp.launch_acp() +pyacp_server.wait(timeout=30) +pyacp_client = pyacp.Client(pyacp_server) + +workflow_reload = ACPWorkflow( + acp_client=pyacp_client, + acph5_file_path=input_acph5_file, + cdb_file_path=input_cdb_file, +) +print_model(workflow_reload.model) +workflow_reload.model.update() +workflow_reload.get_local_cdb_file() diff --git a/examples/README.rst b/examples/README.rst index 0d264eb1d9..8b13789179 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -1,5 +1 @@ -These examples assume that MAPDL and DPF servers are already running. You can start them by running the following command from the root of the PyACP repository: -.. code-block:: - - docker-compose -f docker-compose/docker-compose-extras.yaml up -d diff --git a/examples/solve_class40.py b/examples/solve_class40.py index 1498c9342e..fdf957b5fb 100644 --- a/examples/solve_class40.py +++ b/examples/solve_class40.py @@ -1,8 +1,8 @@ """ .. _solve_class40_example: -Basic PyACP Example -=================== +Class 40 example +================ Define a Composite Lay-up with PyACP, solve the resulting model with PyMAPDL, and run a failure analysis with PyDPF-Composites. @@ -175,8 +175,8 @@ plotter = pyvista.Plotter() plotter.add_mesh(model.mesh.to_pyvista(), color="white") plotter.add_mesh( - oss_hull.elemental_data.to_pyvista( - mesh=model.mesh, component=pyacp.ElementalDataType.ORIENTATION, factor=0.2, culling_factor=5 + oss_hull.elemental_data.orientation.get_pyvista_glyphs( + mesh=model.mesh, factor=0.2, culling_factor=5 ), color="blue", ) @@ -237,24 +237,20 @@ def add_ply(mg, name, ply_material, angle, oss): # Show the thickness of one of the plies model.update() modeling_ply = model.modeling_groups["deck"].modeling_plies["eglass_ud_02mm_0.5"] -modeling_ply.elemental_data.to_pyvista( - mesh=model.mesh, component=pyacp.ElementalDataType.THICKNESS -).plot() +modeling_ply.elemental_data.thickness.get_pyvista_mesh(mesh=model.mesh).plot() # %% # Show the ply offsets, scaled by a factor of 200 plotter = pyvista.Plotter() plotter.add_mesh(model.mesh.to_pyvista(), color="white") plotter.add_mesh( - modeling_ply.nodal_data.to_pyvista( - mesh=model.mesh, component=pyacp.NodalDataType.PLY_OFFSET, factor=200 - ), + modeling_ply.nodal_data.ply_offset.get_pyvista_glyphs(mesh=model.mesh, factor=200), ) plotter.show() # %% # Show the thickness of the entire lay-up -model.elemental_data.to_pyvista(mesh=model.mesh, component=pyacp.ElementalDataType.THICKNESS).plot() +model.elemental_data.thickness.get_pyvista_mesh(mesh=model.mesh).plot() # %% # @@ -297,10 +293,10 @@ def add_ply(mg, name, ply_material, angle, oss): # %% # Import PyMAPDL and connect to its server -from ansys.mapdl.core import Mapdl - -mapdl = Mapdl(ip="localhost", port=50557, timeout=30) +from ansys.mapdl.core import launch_mapdl +mapdl = launch_mapdl() +mapdl.clear() # %% # Load the CDB file into PyMAPDL mapdl.input(str(cdb_file_local_path)) @@ -347,7 +343,7 @@ def add_ply(mg, name, ply_material, angle, oss): # %% # Connect to the server. The ``connect_to_or_start_server`` function # automatically loads the composites plugin. -dpf_server = connect_to_or_start_server(ip="127.0.0.1", port=50558) +dpf_server = connect_to_or_start_server() # %% # Specify the Combined Failure Criterion diff --git a/poetry.lock b/poetry.lock index 071e908e5a..23ff400636 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -202,13 +202,13 @@ protobuf = ">=3.19,<5" [[package]] name = "ansys-api-tools-filetransfer" -version = "0.1.dev3" +version = "0.1.dev5" 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.dev3.tar.gz", hash = "sha256:a09aa8f93e9483b453b08fffbe336fd51e6e3168ae592b5c46ad887333a4ab72"}, - {file = "ansys_api_tools_filetransfer-0.1.dev3-py3-none-any.whl", hash = "sha256:71f357c06724a27d29c358cadd41f58430a461da606369ad3d4e72247301d65d"}, + {file = "ansys-api-tools-filetransfer-0.1.dev5.tar.gz", hash = "sha256:767e32951c9751e49564c7d4adb415d7b6b9d8361e24b2658254ed3792bf3d2d"}, + {file = "ansys_api_tools_filetransfer-0.1.dev5-py3-none-any.whl", hash = "sha256:3f7318a7403ae48d15c7ed68732da1f11a429af654e777eba6386db407e56b72"}, ] [package.dependencies] @@ -467,7 +467,7 @@ doc = ["Sphinx (==7.2.6)", "numpydoc (==1.6.0)", "requests (==2.31.0)", "sphinx- [[package]] name = "ansys-tools-filetransfer" version = "0.1.dev0" -description = "A python client for the ansys filetransfer utility" +description = "A Python client for the ansys filetransfer tool" optional = false python-versions = ">=3.9,<4.0" files = [] @@ -479,15 +479,15 @@ grpcio = "^1.17" [package.extras] build = ["build (>=0.7.0)", "twine (>=3.8)"] -doc = ["Sphinx (>=5.0,<6.0)", "ansys-sphinx-theme (>=0,<1)", "numpydoc (>=1.3,<2.0)", "sphinx-copybutton (>=0.5,<0.6)"] -style = ["mypy (>=0.950,<0.951)", "pre-commit (>=2.19.0,<3.0.0)", "pytest (>=7.1.0)"] +doc = ["Sphinx (>=7.0,<8.0)", "ansys-sphinx-theme (>=0,<1)", "numpydoc (>=1.3,<2.0)", "sphinx-copybutton (>=0.5,<0.6)", "sphinx-design (>=0.5.0,<0.6.0)"] +style = ["mypy (>=1,<2)", "pre-commit (>=3,<4)", "pytest (>=7.1.0)"] tests = ["grpcio-health-checking (>=1.47.0,<2.0.0)", "pytest (>=7.1.0)", "pytest-cov (>=3.0.0)", "tox (>=4,<5)"] [package.source] type = "git" url = "https://github.com/ansys-internal/ansys-tools-filetransfer.git" reference = "main" -resolved_reference = "e658608add9cbf155f552ce6e053ae314210148b" +resolved_reference = "d219bbc5a6847ce37ce05bd94f8caa0758820bf3" [[package]] name = "ansys-tools-local-product-launcher" @@ -509,7 +509,7 @@ grpcio-health-checking = "^1.43" type = "git" url = "https://github.com/ansys-internal/ansys-tools-local-product-launcher.git" reference = "main" -resolved_reference = "ba6f130611e81cdaf7e69bd106367cfc03cc9aa6" +resolved_reference = "70ece6ac8f98af9a06111712afce601ed68df5bd" [[package]] name = "ansys-tools-path" @@ -730,19 +730,22 @@ testing = ["pytest", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pyte [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -1571,13 +1574,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.113.0" +version = "2.115.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-python-client-2.113.0.tar.gz", hash = "sha256:bcffbc8ffbad631f699cf85aa91993f3dc03060b234ca9e6e2f9135028bd9b52"}, - {file = "google_api_python_client-2.113.0-py2.py3-none-any.whl", hash = "sha256:25659d488df6c8a69615b2a510af0e63b4c47ab2cb87d71c1e13b28715906e27"}, + {file = "google-api-python-client-2.115.0.tar.gz", hash = "sha256:96af11376535236ba600ebbe23588cfe003ec9b74e66dd6ddb53aa3ec87e1b52"}, + {file = "google_api_python_client-2.115.0-py2.py3-none-any.whl", hash = "sha256:26178e33684763099142e2cad201057bd27d4efefd859a495aac21ab3e6129c2"}, ] [package.dependencies] @@ -1739,13 +1742,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.92.9" +version = "6.96.4" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.92.9-py3-none-any.whl", hash = "sha256:8c1ab9f3c883fe63a712bb6c8c1b5be4185cad52775cd7703c040fc0d0111572"}, - {file = "hypothesis-6.92.9.tar.gz", hash = "sha256:629f31788243559d35d3101ef8e94caf736cf8efaad3f0dd66ec7dbb31b8ef19"}, + {file = "hypothesis-6.96.4-py3-none-any.whl", hash = "sha256:2beb7a148e95a2067563bcca017d71cc286805c792e43ec5cb155ed6d0a1990d"}, + {file = "hypothesis-6.96.4.tar.gz", hash = "sha256:3b0d080bfd3b303e91388507ac7edebd7039ffcc96ac2cfcdc3c45806352c09f"}, ] [package.dependencies] @@ -1855,13 +1858,13 @@ files = [ [[package]] name = "ipykernel" -version = "6.28.0" +version = "6.29.0" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" files = [ - {file = "ipykernel-6.28.0-py3-none-any.whl", hash = "sha256:c6e9a9c63a7f4095c0a22a79f765f079f9ec7be4f2430a898ddea889e8665661"}, - {file = "ipykernel-6.28.0.tar.gz", hash = "sha256:69c11403d26de69df02225916f916b37ea4b9af417da0a8c827f84328d88e5f3"}, + {file = "ipykernel-6.29.0-py3-none-any.whl", hash = "sha256:076663ca68492576f051e4af7720d33f34383e655f2be0d544c8b1c9de915b2f"}, + {file = "ipykernel-6.29.0.tar.gz", hash = "sha256:b5dd3013cab7b330df712891c96cd1ab868c27a7159e606f762015e9bf8ceb3f"}, ] [package.dependencies] @@ -1884,7 +1887,7 @@ cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] pyqt5 = ["pyqt5"] pyside6 = ["pyside6"] -test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov", "pytest-timeout"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (==0.23.2)", "pytest-cov", "pytest-timeout"] [[package]] name = "ipython" @@ -2007,13 +2010,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.20.0" +version = "4.21.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.20.0-py3-none-any.whl", hash = "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3"}, - {file = "jsonschema-4.20.0.tar.gz", hash = "sha256:4f614fd46d8d61258610998997743ec5492a648b33cf478c1ddc23ed4598a5fa"}, + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, ] [package.dependencies] @@ -2118,13 +2121,13 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p [[package]] name = "jupyter-server" -version = "2.12.4" +version = "2.12.5" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.12.4-py3-none-any.whl", hash = "sha256:a125ae18a60de568f78f55c84dd58759901a18ef279abf0418ac220653ca1320"}, - {file = "jupyter_server-2.12.4.tar.gz", hash = "sha256:41f4a1e6b912cc24a7c6c694851b37d3d8412b180f43d72315fe422cb2b85cc2"}, + {file = "jupyter_server-2.12.5-py3-none-any.whl", hash = "sha256:184a0f82809a8522777cfb6b760ab6f4b1bb398664c5860a27cec696cb884923"}, + {file = "jupyter_server-2.12.5.tar.gz", hash = "sha256:0edb626c94baa22809be1323f9770cf1c00a952b17097592e40d03e6a3951689"}, ] [package.dependencies] @@ -2177,13 +2180,13 @@ test = ["pytest", "pytest-cov", "pytest-html"] [[package]] name = "jupyter-server-terminals" -version = "0.5.1" +version = "0.5.2" description = "A Jupyter Server Extension Providing Terminals." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server_terminals-0.5.1-py3-none-any.whl", hash = "sha256:5e63e947ddd97bb2832db5ef837a258d9ccd4192cd608c1270850ad947ae5dd7"}, - {file = "jupyter_server_terminals-0.5.1.tar.gz", hash = "sha256:16d3be9cf48be6a1f943f3a6c93c033be259cf4779184c66421709cf63dccfea"}, + {file = "jupyter_server_terminals-0.5.2-py3-none-any.whl", hash = "sha256:1b80c12765da979513c42c90215481bbc39bd8ae7c0350b4f85bc3eb58d0fa80"}, + {file = "jupyter_server_terminals-0.5.2.tar.gz", hash = "sha256:396b5ccc0881e550bf0ee7012c6ef1b53edbde69e67cab1d56e89711b46052e8"}, ] [package.dependencies] @@ -2342,71 +2345,71 @@ files = [ [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] @@ -2659,13 +2662,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.14.1" +version = "7.14.2" description = "Converting Jupyter Notebooks" optional = false python-versions = ">=3.8" files = [ - {file = "nbconvert-7.14.1-py3-none-any.whl", hash = "sha256:aa83e3dd27ea38d0c1d908e3ce9518d15fa908dd30521b6d5040bd23f33fffb0"}, - {file = "nbconvert-7.14.1.tar.gz", hash = "sha256:20cba10e0448dc76b3bebfe1adf923663e3b98338daf77b97b42511ef5a88618"}, + {file = "nbconvert-7.14.2-py3-none-any.whl", hash = "sha256:db28590cef90f7faf2ebbc71acd402cbecf13d29176df728c0a9025a49345ea1"}, + {file = "nbconvert-7.14.2.tar.gz", hash = "sha256:a7f8808fd4e082431673ac538400218dd45efd076fbeb07cc6e5aa5a3a4e949e"}, ] [package.dependencies] @@ -2718,13 +2721,13 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] [[package]] name = "nest-asyncio" -version = "1.5.8" +version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" files = [ - {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, - {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] [[package]] @@ -2810,13 +2813,13 @@ test = ["matplotlib", "pytest", "pytest-cov"] [[package]] name = "overrides" -version = "7.4.0" +version = "7.6.0" description = "A decorator to automatically detect mismatch when overriding a method." optional = false python-versions = ">=3.6" files = [ - {file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"}, - {file = "overrides-7.4.0.tar.gz", hash = "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757"}, + {file = "overrides-7.6.0-py3-none-any.whl", hash = "sha256:c36e6635519ea9c5b043b65c36d4b886aee8bd45b7d4681d2a6df0898df4b654"}, + {file = "overrides-7.6.0.tar.gz", hash = "sha256:01e15bbbf15b766f0675c275baa1878bd1c7dc9bc7b9ee13e677cdba93dc1bd9"}, ] [[package]] @@ -2832,13 +2835,13 @@ files = [ [[package]] name = "pandocfilters" -version = "1.5.0" +version = "1.5.1" description = "Utilities for writing pandoc filters in python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, - {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, ] [[package]] @@ -3096,27 +3099,27 @@ files = [ [[package]] name = "psutil" -version = "5.9.7" +version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"}, - {file = "psutil-5.9.7-cp27-none-win32.whl", hash = "sha256:1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"}, - {file = "psutil-5.9.7-cp27-none-win_amd64.whl", hash = "sha256:4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"}, - {file = "psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"}, - {file = "psutil-5.9.7-cp36-cp36m-win32.whl", hash = "sha256:b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"}, - {file = "psutil-5.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"}, - {file = "psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"}, - {file = "psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"}, - {file = "psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"}, - {file = "psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"}, + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, ] [package.extras] @@ -3399,13 +3402,13 @@ files = [ [[package]] name = "pyvista" -version = "0.43.1" +version = "0.43.2" description = "Easier Pythonic interface to VTK" optional = false python-versions = ">=3.8" files = [ - {file = "pyvista-0.43.1-py3-none-any.whl", hash = "sha256:e7e7597c3938ad7e695a1de724ba4d5fcb05cdfa4ac81bb9bee7e75659c25f8a"}, - {file = "pyvista-0.43.1.tar.gz", hash = "sha256:dd2c484d85da2c677a4fd01e801cd8f49a311be602088b0e43cfe9746db0c552"}, + {file = "pyvista-0.43.2-py3-none-any.whl", hash = "sha256:798a62eddb307e09d18e86ee75141fd8bc90d497bb5da9695fdc52f69ebac204"}, + {file = "pyvista-0.43.2.tar.gz", hash = "sha256:8e39040c356669fcd814ca10df775d0dbf17910c116cc6ec5f43431274201f77"}, ] [package.dependencies] @@ -3480,7 +3483,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3488,15 +3490,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3513,7 +3508,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3521,7 +3515,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3695,110 +3688,110 @@ files = [ [[package]] name = "rpds-py" -version = "0.16.2" +version = "0.17.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:509b617ac787cd1149600e731db9274ebbef094503ca25158e6f23edaba1ca8f"}, - {file = "rpds_py-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:413b9c17388bbd0d87a329d8e30c1a4c6e44e2bb25457f43725a8e6fe4161e9e"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2946b120718eba9af2b4dd103affc1164a87b9e9ebff8c3e4c05d7b7a7e274e2"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35ae5ece284cf36464eb160880018cf6088a9ac5ddc72292a6092b6ef3f4da53"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc6a7620ba7639a3db6213da61312cb4aa9ac0ca6e00dc1cbbdc21c2aa6eb57"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8cb6fe8ecdfffa0e711a75c931fb39f4ba382b4b3ccedeca43f18693864fe850"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dace7b26a13353e24613417ce2239491b40a6ad44e5776a18eaff7733488b44"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bdbc5fcb04a7309074de6b67fa9bc4b418ab3fc435fec1f2779a0eced688d04"}, - {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f42e25c016927e2a6b1ce748112c3ab134261fc2ddc867e92d02006103e1b1b7"}, - {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eab36eae3f3e8e24b05748ec9acc66286662f5d25c52ad70cadab544e034536b"}, - {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0474df4ade9a3b4af96c3d36eb81856cb9462e4c6657d4caecfd840d2a13f3c9"}, - {file = "rpds_py-0.16.2-cp310-none-win32.whl", hash = "sha256:84c5a4d1f9dd7e2d2c44097fb09fffe728629bad31eb56caf97719e55575aa82"}, - {file = "rpds_py-0.16.2-cp310-none-win_amd64.whl", hash = "sha256:2bd82db36cd70b3628c0c57d81d2438e8dd4b7b32a6a9f25f24ab0e657cb6c4e"}, - {file = "rpds_py-0.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:adc0c3d6fc6ae35fee3e4917628983f6ce630d513cbaad575b4517d47e81b4bb"}, - {file = "rpds_py-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ec23fcad480e77ede06cf4127a25fc440f7489922e17fc058f426b5256ee0edb"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07aab64e2808c3ebac2a44f67e9dc0543812b715126dfd6fe4264df527556cb6"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4ebb8b20bd09c5ce7884c8f0388801100f5e75e7f733b1b6613c713371feefc"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3d7e2ea25d3517c6d7e5a1cc3702cffa6bd18d9ef8d08d9af6717fc1c700eed"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f28ac0e8e7242d140f99402a903a2c596ab71550272ae9247ad78f9a932b5698"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19f00f57fdd38db4bb5ad09f9ead1b535332dbf624200e9029a45f1f35527ebb"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3da5a4c56953bdbf6d04447c3410309616c54433146ccdb4a277b9cb499bc10e"}, - {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec2e1cf025b2c0f48ec17ff3e642661da7ee332d326f2e6619366ce8e221f018"}, - {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e0441fb4fdd39a230477b2ca9be90868af64425bfe7b122b57e61e45737a653b"}, - {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9f0350ef2fba5f34eb0c9000ea328e51b9572b403d2f7f3b19f24085f6f598e8"}, - {file = "rpds_py-0.16.2-cp311-none-win32.whl", hash = "sha256:5a80e2f83391ad0808b4646732af2a7b67550b98f0cae056cb3b40622a83dbb3"}, - {file = "rpds_py-0.16.2-cp311-none-win_amd64.whl", hash = "sha256:e04e56b4ca7a770593633556e8e9e46579d66ec2ada846b401252a2bdcf70a6d"}, - {file = "rpds_py-0.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5e6caa3809e50690bd92fa490f5c38caa86082c8c3315aa438bce43786d5e90d"}, - {file = "rpds_py-0.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e53b9b25cac9065328901713a7e9e3b12e4f57ef4280b370fbbf6fef2052eef"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af27423662f32d7501a00c5e7342f7dbd1e4a718aea7a239781357d15d437133"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d4dd5fb16eb3825742bad8339d454054261ab59fed2fbac84e1d84d5aae7ba"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e061de3b745fe611e23cd7318aec2c8b0e4153939c25c9202a5811ca911fd733"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b811d182ad17ea294f2ec63c0621e7be92a1141e1012383461872cead87468f"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5552f328eaef1a75ff129d4d0c437bf44e43f9436d3996e8eab623ea0f5fcf73"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dcbe1f8dd179e4d69b70b1f1d9bb6fd1e7e1bdc9c9aad345cdeb332e29d40748"}, - {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8aad80645a011abae487d356e0ceb359f4938dfb6f7bcc410027ed7ae4f7bb8b"}, - {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6f5549d6ed1da9bfe3631ca9483ae906f21410be2445b73443fa9f017601c6f"}, - {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d452817e0d9c749c431a1121d56a777bd7099b720b3d1c820f1725cb40928f58"}, - {file = "rpds_py-0.16.2-cp312-none-win32.whl", hash = "sha256:888a97002e986eca10d8546e3c8b97da1d47ad8b69726dcfeb3e56348ebb28a3"}, - {file = "rpds_py-0.16.2-cp312-none-win_amd64.whl", hash = "sha256:d8dda2a806dfa4a9b795950c4f5cc56d6d6159f7d68080aedaff3bdc9b5032f5"}, - {file = "rpds_py-0.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:071980663c273bf3d388fe5c794c547e6f35ba3335477072c713a3176bf14a60"}, - {file = "rpds_py-0.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:726ac36e8a3bb8daef2fd482534cabc5e17334052447008405daca7ca04a3108"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9e557db6a177470316c82f023e5d571811c9a4422b5ea084c85da9aa3c035fc"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90123853fc8b1747f80b0d354be3d122b4365a93e50fc3aacc9fb4c2488845d6"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a61f659665a39a4d17d699ab3593d7116d66e1e2e3f03ef3fb8f484e91908808"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc97f0640e91d7776530f06e6836c546c1c752a52de158720c4224c9e8053cad"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a54e99a2b9693a37ebf245937fd6e9228b4cbd64b9cc961e1f3391ec6c7391"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4b677d929cf1f6bac07ad76e0f2d5de367e6373351c01a9c0a39f6b21b4a8b"}, - {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5ef00873303d678aaf8b0627e111fd434925ca01c657dbb2641410f1cdaef261"}, - {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:349cb40897fd529ca15317c22c0eab67f5ac5178b5bd2c6adc86172045210acc"}, - {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2ddef620e70eaffebed5932ce754d539c0930f676aae6212f8e16cd9743dd365"}, - {file = "rpds_py-0.16.2-cp38-none-win32.whl", hash = "sha256:882ce6e25e585949c3d9f9abd29202367175e0aab3aba0c58c9abbb37d4982ff"}, - {file = "rpds_py-0.16.2-cp38-none-win_amd64.whl", hash = "sha256:f4bd4578e44f26997e9e56c96dedc5f1af43cc9d16c4daa29c771a00b2a26851"}, - {file = "rpds_py-0.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:69ac7ea9897ec201ce68b48582f3eb34a3f9924488a5432a93f177bf76a82a7e"}, - {file = "rpds_py-0.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a9880b4656efe36ccad41edc66789e191e5ee19a1ea8811e0aed6f69851a82f4"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee94cb58c0ba2c62ee108c2b7c9131b2c66a29e82746e8fa3aa1a1effbd3dcf1"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24f7a2eb3866a9e91f4599851e0c8d39878a470044875c49bd528d2b9b88361c"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca57468da2d9a660bcf8961637c85f2fbb2aa64d9bc3f9484e30c3f9f67b1dd7"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccd4e400309e1f34a5095bf9249d371f0fd60f8a3a5c4a791cad7b99ce1fd38d"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80443fe2f7b3ea3934c5d75fb0e04a5dbb4a8e943e5ff2de0dec059202b70a8b"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d6a9f052e72d493efd92a77f861e45bab2f6be63e37fa8ecf0c6fd1a58fedb0"}, - {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:35953f4f2b3216421af86fd236b7c0c65935936a94ea83ddbd4904ba60757773"}, - {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:981d135c7cdaf6cd8eadae1c950de43b976de8f09d8e800feed307140d3d6d00"}, - {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d0dd7ed2f16df2e129496e7fbe59a34bc2d7fc8db443a606644d069eb69cbd45"}, - {file = "rpds_py-0.16.2-cp39-none-win32.whl", hash = "sha256:703d95c75a72e902544fda08e965885525e297578317989fd15a6ce58414b41d"}, - {file = "rpds_py-0.16.2-cp39-none-win_amd64.whl", hash = "sha256:e93ec1b300acf89730cf27975ef574396bc04edecc358e9bd116fb387a123239"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:44627b6ca7308680a70766454db5249105fa6344853af6762eaad4158a2feebe"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3f91df8e6dbb7360e176d1affd5fb0246d2b88d16aa5ebc7db94fd66b68b61da"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d904c5693e08bad240f16d79305edba78276be87061c872a4a15e2c301fa2c0"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:290a81cfbe4673285cdf140ec5cd1658ffbf63ab359f2b352ebe172e7cfa5bf0"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b634c5ec0103c5cbebc24ebac4872b045cccb9456fc59efdcf6fe39775365bd2"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a297a4d08cc67c7466c873c78039d87840fb50d05473db0ec1b7b03d179bf322"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2e75e17bd0bb66ee34a707da677e47c14ee51ccef78ed6a263a4cc965a072a1"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1b9d9260e06ea017feb7172976ab261e011c1dc2f8883c7c274f6b2aabfe01a"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:162d7cd9cd311c1b0ff1c55a024b8f38bd8aad1876b648821da08adc40e95734"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:9b32f742ce5b57201305f19c2ef7a184b52f6f9ba6871cc042c2a61f0d6b49b8"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac08472f41ea77cd6a5dae36ae7d4ed3951d6602833af87532b556c1b4601d63"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495a14b72bbe217f2695dcd9b5ab14d4f8066a00f5d209ed94f0aca307f85f6e"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:8d6b6937ae9eac6d6c0ca3c42774d89fa311f55adff3970fb364b34abde6ed3d"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a61226465bda9283686db8f17d02569a98e4b13c637be5a26d44aa1f1e361c2"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5cf6af100ffb5c195beec11ffaa8cf8523057f123afa2944e6571d54da84cdc9"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6df15846ee3fb2e6397fe25d7ca6624af9f89587f3f259d177b556fed6bebe2c"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1be2f033df1b8be8c3167ba3c29d5dca425592ee31e35eac52050623afba5772"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96f957d6ab25a78b9e7fc9749d754b98eac825a112b4e666525ce89afcbd9ed5"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:088396c7c70e59872f67462fcac3ecbded5233385797021976a09ebd55961dfe"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4c46ad6356e1561f2a54f08367d1d2e70a0a1bb2db2282d2c1972c1d38eafc3b"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:47713dc4fce213f5c74ca8a1f6a59b622fc1b90868deb8e8e4d993e421b4b39d"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f811771019f063bbd0aa7bb72c8a934bc13ebacb4672d712fc1639cfd314cccc"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f19afcfc0dd0dca35694df441e9b0f95bc231b512f51bded3c3d8ca32153ec19"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4b682c5775d6a3d21e314c10124599976809455ee67020e8e72df1769b87bc3"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c647ca87fc0ebe808a41de912e9a1bfef9acb85257e5d63691364ac16b81c1f0"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:302bd4983bbd47063e452c38be66153760112f6d3635c7eeefc094299fa400a9"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf721ede3eb7b829e4a9b8142bd55db0bdc82902720548a703f7e601ee13bdc3"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:358dafc89ce3894c7f486c615ba914609f38277ef67f566abc4c854d23b997fa"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cad0f59ee3dc35526039f4bc23642d52d5f6616b5f687d846bfc6d0d6d486db0"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cffa76b385dfe1e38527662a302b19ffb0e7f5cf7dd5e89186d2c94a22dd9d0c"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:83640a5d7cd3bff694747d50436b8b541b5b9b9782b0c8c1688931d6ee1a1f2d"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:ed99b4f7179d2111702020fd7d156e88acd533f5a7d3971353e568b6051d5c97"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4022b9dc620e14f30201a8a73898a873c8e910cb642bcd2f3411123bc527f6ac"}, - {file = "rpds_py-0.16.2.tar.gz", hash = "sha256:781ef8bfc091b19960fc0142a23aedadafa826bc32b433fdfe6fd7f964d7ef44"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, + {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, + {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, + {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, + {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, + {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, + {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, + {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, + {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, + {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, + {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, + {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] [[package]] @@ -3817,45 +3810,45 @@ pyasn1 = ">=0.1.3" [[package]] name = "scipy" -version = "1.11.4" +version = "1.12.0" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, - {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, - {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, - {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, - {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, - {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, - {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, - {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, - {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, - {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, - {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, + {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, + {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, + {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, + {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, + {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, + {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, + {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, + {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, + {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, ] [package.dependencies] -numpy = ">=1.21.6,<1.28.0" +numpy = ">=1.22.4,<1.29.0" [package.extras] dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "scooby" @@ -4083,56 +4076,50 @@ sphinx = ">=4" [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.7" +version = "1.0.8" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, - {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.5" +version = "1.0.6" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, - {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.4" +version = "2.0.5" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, - {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] @@ -4151,38 +4138,34 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.6" +version = "1.0.7" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, - {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.9" +version = "1.1.10" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, - {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] @@ -4340,13 +4323,13 @@ trame-server = ">=2.13.1" [[package]] name = "trame-client" -version = "2.14.2" +version = "2.15.0" description = "Internal client of trame" optional = false python-versions = "*" files = [ - {file = "trame-client-2.14.2.tar.gz", hash = "sha256:81c35c26a98e4dca8873b84a7dcfe53d39594f827e12f9d217db040ef9c74f35"}, - {file = "trame_client-2.14.2-py3-none-any.whl", hash = "sha256:050116b584ebb9e5e599ce8c9bcdc3c1028fe436e969ef38c50206e329572e7f"}, + {file = "trame-client-2.15.0.tar.gz", hash = "sha256:eb2cd18f2a29c0c45b982404de092fb4c989d1eff3489a4204ccf097fa2afeed"}, + {file = "trame_client-2.15.0-py3-none-any.whl", hash = "sha256:fa58046e241df35779327209a4ec17fc29b7d8d557e8814e954ea78dffa49de4"}, ] [package.extras] @@ -4369,13 +4352,13 @@ wslink = ">=1.12.2" [[package]] name = "trame-vtk" -version = "2.6.3" +version = "2.7.0" description = "VTK widgets for trame" optional = false python-versions = "*" files = [ - {file = "trame-vtk-2.6.3.tar.gz", hash = "sha256:3314f6a9a5ed844ef23f0296bb221fe05d9fc3ec9bf7a853b6b433da41000d52"}, - {file = "trame_vtk-2.6.3-py3-none-any.whl", hash = "sha256:6bc90437695eceedc52f2845554b218e2460db650ce3e6d5f9cf07759796d4f7"}, + {file = "trame-vtk-2.7.0.tar.gz", hash = "sha256:f6476e4ffc3bf7171523153450d4762f3e31b9654133f941c92700573780b6b2"}, + {file = "trame_vtk-2.7.0-py3-none-any.whl", hash = "sha256:a517f410741800ed030d49ef425c823b52b26bc92b328525dabbc093c4daddd3"}, ] [package.dependencies] @@ -4383,13 +4366,13 @@ trame-client = "*" [[package]] name = "trame-vuetify" -version = "2.3.1" +version = "2.4.2" description = "Vuetify widgets for trame" optional = false python-versions = "*" files = [ - {file = "trame-vuetify-2.3.1.tar.gz", hash = "sha256:9775c4833ec87ba400c8cdb4eef1dce24396fd841968369563bb34bb0b60ee4a"}, - {file = "trame_vuetify-2.3.1-py3-none-any.whl", hash = "sha256:455b179143f44e71719f9b8feeb36ec9aabf1d25e79c637a425c8cb153405335"}, + {file = "trame-vuetify-2.4.2.tar.gz", hash = "sha256:85989236e9fb743448e807fe7ee341b8d65c0970cf95669cc5924c42053df9f6"}, + {file = "trame_vuetify-2.4.2-py3-none-any.whl", hash = "sha256:7723a620a604aed5c2d9c4d577a76d8dfc8b41ba8f1b49b8b2ba02ce45961a22"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 991fdd524b..4f1d533a14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,7 +126,7 @@ src_paths = ["doc", "src", "tests"] convention = "numpy" [tool.codespell] -skip = '*.cdb' +skip = '*.cdb,*.dat' ignore-words-list = 'ans,uptodate,notuptodate,eyt' quiet-level = 3 diff --git a/src/ansys/acp/core/__init__.py b/src/ansys/acp/core/__init__.py index 0de10d4ce8..5aa3f3ac7e 100644 --- a/src/ansys/acp/core/__init__.py +++ b/src/ansys/acp/core/__init__.py @@ -1,6 +1,7 @@ import importlib.metadata from ._client import Client +from ._model_printer import get_model_tree, print_model from ._server import DirectLaunchConfig, DockerComposeLaunchConfig, LaunchMode, launch_acp from ._tree_objects import ( AnalysisPly, @@ -31,6 +32,7 @@ ParallelSelectionRule, ProductionPly, Rosette, + ScalarData, Sensor, SphericalSelectionRule, Stackup, @@ -39,8 +41,11 @@ TubeSelectionRule, UnitSystemType, VariableOffsetSelectionRule, + VectorData, VirtualGeometry, ) +from ._utils.example_helpers import ExampleKeys, get_example_file +from ._workflow import ACPWorkflow, get_composite_post_processing_files, get_dpf_unit_system __version__ = importlib.metadata.version(__name__.replace(".", "-")) @@ -90,4 +95,13 @@ "Sensor", "ElementalDataType", "NodalDataType", + "ExampleKeys", + "get_example_file", + "ACPWorkflow", + "get_composite_post_processing_files", + "get_model_tree", + "print_model", + "ScalarData", + "VectorData", + "get_dpf_unit_system", ] diff --git a/src/ansys/acp/core/_client.py b/src/ansys/acp/core/_client.py index 6ffa866796..4375ad8cb7 100644 --- a/src/ansys/acp/core/_client.py +++ b/src/ansys/acp/core/_client.py @@ -2,10 +2,7 @@ import os import pathlib -import shutil -import tempfile from typing import Any, cast -import uuid from ansys.api.acp.v0 import control_pb2_grpc, model_pb2_grpc from ansys.api.acp.v0.base_pb2 import CollectionPath, DeleteRequest, Empty, ListRequest @@ -28,30 +25,35 @@ class Client: The ACP gRPC server to which the ``Client`` connects. """ - def __init__(self, server: ServerProtocol): + def __init__(self, server: ServerProtocol) -> None: self._channel = server.channels[ServerKey.MAIN] if ServerKey.FILE_TRANSFER in server.channels: self._ft_client: FileTransferClient | None = FileTransferClient( server.channels[ServerKey.FILE_TRANSFER] ) - self._tmp_dir = None else: self._ft_client = None - self._tmp_dir = tempfile.TemporaryDirectory() + + @property + def is_remote(self) -> bool: + """Whether the client is connected to a remote server.""" + return self._ft_client is not None def upload_file(self, local_path: _PATH) -> pathlib.PurePath: - if self._ft_client is None: - assert self._tmp_dir is not None - # TODO: The '_tmp_dir', and file tracking / up-/download in general - # should probably be handled by the local server itself. - # For now, we just do it client-side. - dest_dir = pathlib.Path(self._tmp_dir.name) / uuid.uuid4().hex - dest_dir.mkdir(parents=True) - filename = os.path.basename(local_path) - res_path = dest_dir / filename - shutil.copyfile(local_path, res_path) - return pathlib.Path(res_path) + """Upload local file. + + If the server is remote, uploads the file to the server and returns the path + on the server. + If the server is local, does nothing and returns the input path. + + Parameters + ---------- + local_path : + Path of the file to be uploaded. + """ + if self._ft_client is None: + return pathlib.Path(local_path) else: remote_filename = os.path.basename(local_path) self._ft_client.upload_file( @@ -61,9 +63,20 @@ def upload_file(self, local_path: _PATH) -> pathlib.PurePath: return pathlib.PurePosixPath(remote_filename) def download_file(self, remote_filename: _PATH, local_path: _PATH) -> None: - if self._ft_client is None: - shutil.copyfile(remote_filename, local_path) - else: + """ + Download a file from the server. + + If the server is remote, download the file to the local path. + If the server is local, do nothing. + + Parameters + ---------- + remote_filename : + Path of the file to be downloaded. + local_path : + Path of the local file to be created. + """ + if self._ft_client is not None: self._ft_client.download_file( remote_filename=str(remote_filename), local_filename=str(local_path) ) diff --git a/src/ansys/acp/core/_model_printer.py b/src/ansys/acp/core/_model_printer.py new file mode 100644 index 0000000000..6b58ac8fbf --- /dev/null +++ b/src/ansys/acp/core/_model_printer.py @@ -0,0 +1,127 @@ +import os +from typing import Optional + +from ._tree_objects.model import Model + + +class Node: + """A node in a tree representation of the model. + + Parameters + ---------- + label: + Label of the node. + children: + Children of the node. + """ + + def __init__(self, label: str, children: Optional[list["Node"]] = None): + self.label = label + self.children: list["Node"] = children if children else [] + + def __str__(self, level: Optional[int] = 0) -> str: + assert level is not None + four_spaces = " " + ret = four_spaces * level + self.label + os.linesep + for child in self.children: + ret += child.__str__(level + 1) + return ret + + +def _replace_underscores_and_capitalize(input_string: str) -> str: + return " ".join(part.capitalize() for part in input_string.split("_")) + + +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: + """Print a tree representation of the model. + + + Parameters + ---------- + model: + pyACP model + + """ + return print(get_model_tree(model)) + + +def get_model_tree(model: Model) -> Node: + """Get a tree representation of the model. + + Returns the root node. + + Parameters + ---------- + model: + pyACP model. + """ + 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 Table") + 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 diff --git a/src/ansys/acp/core/_tree_objects/__init__.py b/src/ansys/acp/core/_tree_objects/__init__.py index 3442883f83..8b4ace9ee2 100644 --- a/src/ansys/acp/core/_tree_objects/__init__.py +++ b/src/ansys/acp/core/_tree_objects/__init__.py @@ -1,3 +1,4 @@ +from ._mesh_data import ScalarData, VectorData from .analysis_ply import AnalysisPly from .boolean_selection_rule import BooleanSelectionRule from .cad_component import CADComponent @@ -69,4 +70,6 @@ "EdgeSetType", "ElementalDataType", "NodalDataType", + "ScalarData", + "VectorData", ] diff --git a/src/ansys/acp/core/_tree_objects/_mesh_data.py b/src/ansys/acp/core/_tree_objects/_mesh_data.py index 1e7675eadc..9991d90f24 100644 --- a/src/ansys/acp/core/_tree_objects/_mesh_data.py +++ b/src/ansys/acp/core/_tree_objects/_mesh_data.py @@ -12,6 +12,7 @@ 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 .._utils.property_protocols import ReadOnlyProperty from .base import TreeObject from .enums import ( elemental_data_type_from_pb, @@ -28,9 +29,240 @@ "NodalData", "elemental_data_property", "nodal_data_property", + "ScalarData", + "VectorData", ] +@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, + **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) + 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]: + """The values as a numpy array.""" + return self._values + + @property + def component_name(self) -> str: + """The 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]: + """The values as a numpy array.""" + return self._values + + @property + def component_name(self) -> str: + """The name of the component.""" + return self._component_name + + def get_pyvista_glyphs( + self, + *, + mesh: MeshData, + culling_factor: int = 1, + **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. + 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, + **kwargs, + ) + + +def _check_field_type(klass: Any, field_name: str, actual_field_type: str) -> None: + """Checks 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] + 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 MeshDataBase: """ @@ -38,120 +270,92 @@ class MeshDataBase: from a protobuf response and the conversion to a PyVista object. """ - _LABEL_FIELD_NAME: ClassVar[str] - _PYVISTA_FIELD_NAME: ClassVar[str] + _LABEL_AND_PYVISTA_FIELD_NAMES: ClassVar[_LabelAndPyvistaFieldNames] _FIELD_NAME_FROM_PB_VALUE: ClassVar[typing.Callable[[int], str]] _PB_VALUE_FROM_FIELD_NAME: ClassVar[typing.Callable[[str], int]] @classmethod def _field_names(cls) -> list[str]: return [ - field.name for field in dataclasses.fields(cls) if field.name != cls._LABEL_FIELD_NAME + 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_FIELD_NAME: to_numpy(response.labels), + 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) - kwargs[field_name] = cast( + values = cast( npt.NDArray[np.float64], dataarray_to_numpy(array, dtype=np.float64) ) # todo: handle other dtypes - return cls(**kwargs) + 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 to_pyvista( + def get_pyvista_mesh( self, - *, mesh: MeshData, - component: str | None = None, - culling_factor: int = 1, - **kwargs: Any, - ) -> PolyData | UnstructuredGrid: - """Convert the mesh data to a PyVista object. + ) -> UnstructuredGrid: + """Get a pyvista mesh with all data. Parameters ---------- mesh : The mesh to which the data is associated. - component : - The name of the data attribute to add to the PyVista object. - If `None`, all data attributes are added. If the data attribute - contains vector data, the PyVista object will be converted to - arrows. - 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. - kwargs : - Keyword arguments passed to the PyVista object constructor. """ - current_labels = getattr(self, self._LABEL_FIELD_NAME) - mesh_labels = getattr(mesh, self._LABEL_FIELD_NAME) - idx_map = {label: idx for idx, label in enumerate(mesh_labels)} - pv_mesh = mesh.to_pyvista() - - mesh_data_field = getattr(pv_mesh, self._PYVISTA_FIELD_NAME) - - if component is None: - for name in self._field_names(): - values = getattr(self, name) - target_array = self._expand_array( - index_map=idx_map, - array=values, - labels=current_labels, - mesh_labels=mesh_labels, - culling_factor=culling_factor, - ) - mesh_data_field[name] = target_array - else: - values = getattr(self, component) - target_array = self._expand_array( - index_map=idx_map, - array=values, - labels=current_labels, - mesh_labels=mesh_labels, - culling_factor=culling_factor, - ) - mesh_data_field[component] = target_array - if len(target_array.shape) == 2 and target_array.shape[1] == 3: - # handle vector data - magnitude_name = f"{component}_magnitude" - mesh_data_field[magnitude_name] = np.linalg.norm(target_array, axis=-1) - return pv_mesh.glyph(orient=component, scale=magnitude_name, **kwargs) # type: ignore - if kwargs: - raise TypeError( - "The following keyword arguments were not used: " + ", ".join(kwargs.keys()) - ) - return pv_mesh + return _get_pyvista_mesh_with_all_data(mesh_data_base=self, mesh=mesh) - @staticmethod - def _expand_array( - *, - index_map: dict[int, int], - array: npt.NDArray[np.float64], - labels: npt.NDArray[np.int32], - mesh_labels: npt.NDArray[np.int32], - culling_factor: int, - ) -> npt.NDArray[np.float64]: - """Expand the array to the size of the mesh.""" - target_shape = tuple([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, array)): - if idx % culling_factor == 0: - target_array[index_map[label]] = value - return target_array + +_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(MeshDataBase): """Base class for nodal data.""" - node_labels: npt.NDArray[np.int32] - _LABEL_FIELD_NAME: ClassVar[str] = "node_labels" - _PYVISTA_FIELD_NAME: ClassVar[str] = "point_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 @@ -160,9 +364,8 @@ class NodalData(MeshDataBase): class ElementalData(MeshDataBase): """Base class for elemental data.""" - element_labels: npt.NDArray[np.int32] - _LABEL_FIELD_NAME: ClassVar[str] = "element_labels" - _PYVISTA_FIELD_NAME: ClassVar[str] = "cell_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 @@ -175,7 +378,7 @@ class ElementalData(MeshDataBase): def elemental_data_property( wrapped_cls: type[ElementalDataT], -) -> property: +) -> ReadOnlyProperty[ElementalDataT]: """Create a property to get elemental data from a tree object.""" return _mesh_data_property_impl( wrapped_cls=wrapped_cls, @@ -189,7 +392,7 @@ def elemental_data_property( def nodal_data_property( wrapped_cls: type[NodalDataT], -) -> property: +) -> ReadOnlyProperty[NodalDataT]: """Create a property to get nodal data from a tree object.""" return _mesh_data_property_impl( wrapped_cls=wrapped_cls, @@ -206,7 +409,7 @@ def _mesh_data_property_impl( request_name: Literal["GetNodalData", "GetElementalData"], request_type: type[mesh_query_pb2.GetNodalDataRequest] | type[mesh_query_pb2.GetElementalDataRequest], -) -> property: +) -> ReadOnlyProperty[MeshDataT]: """Implementation of the mesh data property helpers.""" def getter(self: TreeObject) -> MeshDataT: diff --git a/src/ansys/acp/core/_tree_objects/analysis_ply.py b/src/ansys/acp/core/_tree_objects/analysis_ply.py index 38667f29c7..f872c52401 100644 --- a/src/ansys/acp/core/_tree_objects/analysis_ply.py +++ b/src/ansys/acp/core/_tree_objects/analysis_ply.py @@ -4,7 +4,6 @@ import dataclasses import numpy as np -import numpy.typing as npt from ansys.api.acp.v0 import analysis_ply_pb2, analysis_ply_pb2_grpc @@ -14,7 +13,14 @@ grpc_link_property_read_only, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + ScalarData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -26,33 +32,33 @@ class AnalysisPlyElementalData(ElementalData): """Represents elemental data for a Analysis Ply.""" - normal: npt.NDArray[np.float64] - orientation: npt.NDArray[np.float64] - reference_direction: npt.NDArray[np.float64] - fiber_direction: npt.NDArray[np.float64] - draped_fiber_direction: npt.NDArray[np.float64] - transverse_direction: npt.NDArray[np.float64] - draped_transverse_direction: npt.NDArray[np.float64] - thickness: npt.NDArray[np.float64] - relative_thickness_correction: npt.NDArray[np.float64] - design_angle: npt.NDArray[np.float64] - shear_angle: npt.NDArray[np.float64] - draped_fiber_angle: npt.NDArray[np.float64] - draped_transverse_angle: npt.NDArray[np.float64] - area: npt.NDArray[np.float64] - price: npt.NDArray[np.float64] - volume: npt.NDArray[np.float64] - mass: npt.NDArray[np.float64] - offset: npt.NDArray[np.float64] - material_1_direction: npt.NDArray[np.float64] - cog: npt.NDArray[np.float64] + normal: VectorData + orientation: VectorData + reference_direction: VectorData + fiber_direction: VectorData + draped_fiber_direction: VectorData + transverse_direction: VectorData + draped_transverse_direction: VectorData + thickness: ScalarData[np.float64] + relative_thickness_correction: ScalarData[np.float64] + design_angle: ScalarData[np.float64] + shear_angle: ScalarData[np.float64] + draped_fiber_angle: ScalarData[np.float64] + draped_transverse_angle: ScalarData[np.float64] + area: ScalarData[np.float64] + price: ScalarData[np.float64] + volume: ScalarData[np.float64] + mass: ScalarData[np.float64] + offset: ScalarData[np.float64] + material_1_direction: VectorData + cog: VectorData @dataclasses.dataclass class AnalysisPlyNodalData(NodalData): """Represents nodal data for a Analysis Ply.""" - ply_offset: npt.NDArray[np.float64] + ply_offset: VectorData @mark_grpc_properties 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 e56f5e3f32..63c04644ab 100644 --- a/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/boolean_selection_rule.py @@ -3,9 +3,6 @@ from collections.abc import Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import boolean_selection_rule_pb2, boolean_selection_rule_pb2_grpc from .._utils.property_protocols import ReadWriteProperty @@ -15,7 +12,13 @@ grpc_data_property_read_only, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .linked_selection_rule import LinkedSelectionRule @@ -32,7 +35,7 @@ class BooleanSelectionRuleElementalData(ElementalData): """Represents elemental data for a Boolean Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass 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 14b632f53d..bbfeb4e97d 100644 --- a/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cutoff_selection_rule.py @@ -3,9 +3,6 @@ from collections.abc import Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import cutoff_selection_rule_pb2, cutoff_selection_rule_pb2_grpc from .._utils.property_protocols import ReadWriteProperty @@ -15,7 +12,13 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .enums import ( @@ -41,7 +44,7 @@ class CutoffSelectionRuleElementalData(ElementalData): """Represents elemental data for a Cutoff Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass 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 1eac58d7ed..e1e13ee357 100644 --- a/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/cylindrical_selection_rule.py @@ -3,9 +3,6 @@ from collections.abc import Collection, Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import cylindrical_selection_rule_pb2, cylindrical_selection_rule_pb2_grpc from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array @@ -16,7 +13,13 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -33,7 +36,7 @@ class CylindricalSelectionRuleElementalData(ElementalData): """Represents elemental data for a Cylindrical Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass diff --git a/src/ansys/acp/core/_tree_objects/element_set.py b/src/ansys/acp/core/_tree_objects/element_set.py index ac0e7e7d18..8588dbeaa6 100644 --- a/src/ansys/acp/core/_tree_objects/element_set.py +++ b/src/ansys/acp/core/_tree_objects/element_set.py @@ -3,9 +3,6 @@ from collections.abc import Collection, Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import element_set_pb2, element_set_pb2_grpc from .._utils.array_conversions import to_1D_int_array, to_tuple_from_1D_array @@ -15,7 +12,13 @@ grpc_data_property_read_only, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -31,7 +34,7 @@ class ElementSetElementalData(ElementalData): """Represents elemental data for an Element Set.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass 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 f5382c3878..0d08ae02fb 100644 --- a/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/geometrical_selection_rule.py @@ -3,9 +3,6 @@ from collections.abc import Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import geometrical_selection_rule_pb2, geometrical_selection_rule_pb2_grpc from .._utils.property_protocols import ReadWriteProperty @@ -16,7 +13,13 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +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 ( @@ -39,7 +42,7 @@ class GeometricalSelectionRuleElementalData(ElementalData): """Represents elemental data for a Geometrical Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass diff --git a/src/ansys/acp/core/_tree_objects/model.py b/src/ansys/acp/core/_tree_objects/model.py index 96302b9508..37494161a0 100644 --- a/src/ansys/acp/core/_tree_objects/model.py +++ b/src/ansys/acp/core/_tree_objects/model.py @@ -54,7 +54,14 @@ grpc_data_property_read_only, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + ScalarData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import TreeObject from .boolean_selection_rule import BooleanSelectionRule from .cad_geometry import CADGeometry @@ -120,15 +127,15 @@ def to_pyvista(self) -> UnstructuredGrid: class ModelElementalData(ElementalData): """Represents elemental data for a Model.""" - normal: npt.NDArray[np.float64] - thickness: npt.NDArray[np.float64] - relative_thickness_correction: npt.NDArray[np.float64] - area: npt.NDArray[np.float64] - price: npt.NDArray[np.float64] - volume: npt.NDArray[np.float64] - mass: npt.NDArray[np.float64] - offset: npt.NDArray[np.float64] - cog: npt.NDArray[np.float64] + normal: VectorData + thickness: ScalarData[np.float64] + relative_thickness_correction: ScalarData[np.float64] + area: ScalarData[np.float64] + price: ScalarData[np.float64] + volume: ScalarData[np.float64] + mass: ScalarData[np.float64] + offset: ScalarData[np.float64] + cog: VectorData @dataclasses.dataclass diff --git a/src/ansys/acp/core/_tree_objects/modeling_group.py b/src/ansys/acp/core/_tree_objects/modeling_group.py index 7d1c8180cd..c6c224b072 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_group.py +++ b/src/ansys/acp/core/_tree_objects/modeling_group.py @@ -4,14 +4,17 @@ import dataclasses from typing import Any -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import 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 -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .modeling_ply import ModelingPly from .object_registry import register @@ -23,7 +26,7 @@ class ModelingGroupElementalData(ElementalData): """Represents elemental data for an Modeling Group.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass diff --git a/src/ansys/acp/core/_tree_objects/modeling_ply.py b/src/ansys/acp/core/_tree_objects/modeling_ply.py index b1c408a845..16a464c905 100644 --- a/src/ansys/acp/core/_tree_objects/modeling_ply.py +++ b/src/ansys/acp/core/_tree_objects/modeling_ply.py @@ -5,10 +5,8 @@ from typing import Any, Callable import numpy as np -import numpy.typing as npt from typing_extensions import Self -from ansys.acp.core._tree_objects.base import CreatableTreeObject from ansys.api.acp.v0 import modeling_ply_pb2, modeling_ply_pb2_grpc, production_ply_pb2_grpc from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array @@ -22,7 +20,14 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + ScalarData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .enums import ( @@ -53,32 +58,32 @@ class ModelingPlyElementalData(ElementalData): """Represents elemental data for a Modeling Ply.""" - normal: npt.NDArray[np.float64] - orientation: npt.NDArray[np.float64] - reference_direction: npt.NDArray[np.float64] - fiber_direction: npt.NDArray[np.float64] - draped_fiber_direction: npt.NDArray[np.float64] - transverse_direction: npt.NDArray[np.float64] - draped_transverse_direction: npt.NDArray[np.float64] - thickness: npt.NDArray[np.float64] - relative_thickness_correction: npt.NDArray[np.float64] - design_angle: npt.NDArray[np.float64] - shear_angle: npt.NDArray[np.float64] - draped_fiber_angle: npt.NDArray[np.float64] - draped_transverse_angle: npt.NDArray[np.float64] - area: npt.NDArray[np.float64] - price: npt.NDArray[np.float64] - volume: npt.NDArray[np.float64] - mass: npt.NDArray[np.float64] - offset: npt.NDArray[np.float64] - cog: npt.NDArray[np.float64] + normal: VectorData + orientation: VectorData + reference_direction: VectorData + fiber_direction: VectorData + draped_fiber_direction: VectorData + transverse_direction: VectorData + draped_transverse_direction: VectorData + thickness: ScalarData[np.float64] + relative_thickness_correction: ScalarData[np.float64] + design_angle: ScalarData[np.float64] + shear_angle: ScalarData[np.float64] + draped_fiber_angle: ScalarData[np.float64] + draped_transverse_angle: ScalarData[np.float64] + area: ScalarData[np.float64] + price: ScalarData[np.float64] + volume: ScalarData[np.float64] + mass: ScalarData[np.float64] + offset: ScalarData[np.float64] + cog: VectorData @dataclasses.dataclass class ModelingPlyNodalData(NodalData): """Represents nodal data for a Modeling Ply.""" - ply_offset: npt.NDArray[np.float64] + ply_offset: VectorData class TaperEdge(GenericEdgePropertyType): 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 fc88e311d7..83037b81b4 100644 --- a/src/ansys/acp/core/_tree_objects/oriented_selection_set.py +++ b/src/ansys/acp/core/_tree_objects/oriented_selection_set.py @@ -3,9 +3,6 @@ from collections.abc import Iterable, Sequence import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import oriented_selection_set_pb2, oriented_selection_set_pb2_grpc from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array @@ -19,7 +16,13 @@ grpc_data_property_read_only, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .boolean_selection_rule import BooleanSelectionRule from .cylindrical_selection_rule import CylindricalSelectionRule @@ -51,9 +54,9 @@ class OrientedSelectionSetElementalData(ElementalData): """Represents elemental data for an Oriented Selection Set.""" - normal: npt.NDArray[np.float64] - orientation: npt.NDArray[np.float64] - reference_direction: npt.NDArray[np.float64] + normal: VectorData + orientation: VectorData + reference_direction: VectorData @dataclasses.dataclass 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 f2594b6ef1..1808aff7a6 100644 --- a/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/parallel_selection_rule.py @@ -3,9 +3,6 @@ from collections.abc import Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import parallel_selection_rule_pb2, parallel_selection_rule_pb2_grpc from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array @@ -16,7 +13,13 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -33,7 +36,7 @@ class ParallelSelectionRuleElementalData(ElementalData): """Represents elemental data for a Parallel Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass diff --git a/src/ansys/acp/core/_tree_objects/production_ply.py b/src/ansys/acp/core/_tree_objects/production_ply.py index 283dc34616..4c3bdcab8b 100644 --- a/src/ansys/acp/core/_tree_objects/production_ply.py +++ b/src/ansys/acp/core/_tree_objects/production_ply.py @@ -4,7 +4,6 @@ import dataclasses import numpy as np -import numpy.typing as npt from ansys.api.acp.v0 import analysis_ply_pb2_grpc, production_ply_pb2, production_ply_pb2_grpc @@ -15,7 +14,14 @@ grpc_link_property_read_only, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + ScalarData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .analysis_ply import AnalysisPly from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb @@ -28,32 +34,32 @@ class ProductionPlyElementalData(ElementalData): """Represents elemental data for a Production Ply.""" - normal: npt.NDArray[np.float64] - orientation: npt.NDArray[np.float64] - reference_direction: npt.NDArray[np.float64] - fiber_direction: npt.NDArray[np.float64] - draped_fiber_direction: npt.NDArray[np.float64] - transverse_direction: npt.NDArray[np.float64] - draped_transverse_direction: npt.NDArray[np.float64] - thickness: npt.NDArray[np.float64] - relative_thickness_correction: npt.NDArray[np.float64] - design_angle: npt.NDArray[np.float64] - shear_angle: npt.NDArray[np.float64] - draped_fiber_angle: npt.NDArray[np.float64] - draped_transverse_angle: npt.NDArray[np.float64] - area: npt.NDArray[np.float64] - price: npt.NDArray[np.float64] - volume: npt.NDArray[np.float64] - mass: npt.NDArray[np.float64] - offset: npt.NDArray[np.float64] - cog: npt.NDArray[np.float64] + normal: VectorData + orientation: VectorData + reference_direction: VectorData + fiber_direction: VectorData + draped_fiber_direction: VectorData + transverse_direction: VectorData + draped_transverse_direction: VectorData + thickness: ScalarData[np.float64] + relative_thickness_correction: ScalarData[np.float64] + design_angle: ScalarData[np.float64] + shear_angle: ScalarData[np.float64] + draped_fiber_angle: ScalarData[np.float64] + draped_transverse_angle: ScalarData[np.float64] + area: ScalarData[np.float64] + price: ScalarData[np.float64] + volume: ScalarData[np.float64] + mass: ScalarData[np.float64] + offset: ScalarData[np.float64] + cog: VectorData @dataclasses.dataclass class ProductionPlyNodalData(NodalData): """Represents nodal data for a Production Ply.""" - ply_offset: npt.NDArray[np.float64] + ply_offset: VectorData @mark_grpc_properties 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 de867356d7..f96b6b8e3b 100644 --- a/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/spherical_selection_rule.py @@ -3,9 +3,6 @@ from collections.abc import Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import spherical_selection_rule_pb2, spherical_selection_rule_pb2_grpc from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array @@ -16,7 +13,13 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -33,7 +36,7 @@ class SphericalSelectionRuleElementalData(ElementalData): """Represents elemental data for a Spherical Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass 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 aa537aef69..167a1a7c6a 100644 --- a/src/ansys/acp/core/_tree_objects/tube_selection_rule.py +++ b/src/ansys/acp/core/_tree_objects/tube_selection_rule.py @@ -3,9 +3,6 @@ from collections.abc import Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import tube_selection_rule_pb2, tube_selection_rule_pb2_grpc from .._utils.array_conversions import to_1D_double_array, to_tuple_from_1D_array @@ -16,7 +13,13 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .enums import status_type_from_pb @@ -33,7 +36,7 @@ class TubeSelectionRuleElementalData(ElementalData): """Represents elemental data for a Tube Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass 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 c1afc8956b..eca949dc28 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 @@ -3,9 +3,6 @@ from collections.abc import Iterable import dataclasses -import numpy as np -import numpy.typing as npt - from ansys.api.acp.v0 import ( variable_offset_selection_rule_pb2, variable_offset_selection_rule_pb2_grpc, @@ -19,7 +16,13 @@ grpc_link_property, mark_grpc_properties, ) -from ._mesh_data import ElementalData, NodalData, elemental_data_property, nodal_data_property +from ._mesh_data import ( + ElementalData, + NodalData, + VectorData, + elemental_data_property, + nodal_data_property, +) from .base import CreatableTreeObject, IdTreeObject from .edge_set import EdgeSet from .element_set import ElementSet @@ -38,7 +41,7 @@ class VariableOffsetSelectionRuleElementalData(ElementalData): """Represents elemental data for a VariableOffset Selection Rule.""" - normal: npt.NDArray[np.float64] + normal: VectorData @dataclasses.dataclass diff --git a/src/ansys/acp/core/_utils/example_helpers.py b/src/ansys/acp/core/_utils/example_helpers.py new file mode 100644 index 0000000000..1bf8cf6232 --- /dev/null +++ b/src/ansys/acp/core/_utils/example_helpers.py @@ -0,0 +1,57 @@ +import dataclasses +from enum import Enum, auto +import pathlib +import urllib.request + +__all__ = ["ExampleKeys", "get_example_file"] + +_EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" + + +@dataclasses.dataclass +class _ExampleLocation: + directory: str + filename: str + + +class ExampleKeys(Enum): + """ + Keys for the example files. + """ + + BASIC_FLAT_PLATE_CDB = auto() + BASIC_FLAT_PLATE_ACPH5 = auto() + + +EXAMPLE_FILES: dict[ExampleKeys, _ExampleLocation] = { + ExampleKeys.BASIC_FLAT_PLATE_CDB: _ExampleLocation( + directory="basic_flat_plate_example", filename="flat_plate_input.dat" + ), + ExampleKeys.BASIC_FLAT_PLATE_ACPH5: _ExampleLocation( + directory="basic_flat_plate_example", filename="flat_plate.acph5" + ), +} + + +def get_example_file(example_key: ExampleKeys, working_directory: pathlib.Path) -> pathlib.Path: + """Downloads an example file from the example-data repo to the working directory. + + Parameters + ---------- + example_key + Key for the example file. + working_directory + Working directory to download the example file to. + """ + example_location = EXAMPLE_FILES[example_key] + _download_file(example_location, working_directory / example_location.filename) + 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 _download_file(example_location: _ExampleLocation, local_path: pathlib.Path) -> None: + file_url = _get_file_url(example_location) + urllib.request.urlretrieve(file_url, local_path) diff --git a/src/ansys/acp/core/_workflow.py b/src/ansys/acp/core/_workflow.py new file mode 100644 index 0000000000..baa8edcb36 --- /dev/null +++ b/src/ansys/acp/core/_workflow.py @@ -0,0 +1,287 @@ +import pathlib +import shutil +import tempfile +import typing +from typing import Callable, Optional, Protocol + +from . import UnitSystemType +from ._client import Client +from ._tree_objects import Model +from ._typing_helper import PATH + +# Avoid dependencies on pydpf-composites and dpf-core if it is not used +if typing.TYPE_CHECKING: + 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: Optional[pathlib.Path] = 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 = 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_client: Client): + self._local_working_directory = local_working_directory + self._acp_client = acp_client + + 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_client.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_client.upload_file(local_path=path) + + +def _get_file_transfer_strategy( + acp_client: Client, local_working_dir: _LocalWorkingDir +) -> _FileStrategy: + if acp_client.is_remote: + return _RemoteFileTransferStrategy( + local_working_directory=local_working_dir, + acp_client=acp_client, + ) + else: + return _LocalFileTransferStrategy( + local_working_directory=local_working_dir, + ) + + +# Todo: Add automated tests for local and remote workflow +# Todo: update logic when cdb is part of the acph5 +class ACPWorkflow: + r"""Instantiate an ACP Workflow. + + Supports starting from a \*.cbd or \*.acph5 file + + Parameters + ---------- + acp_client + The ACP Client + local_working_directory: + The local working directory. If None, a temporary directory will be created. + cdb_file_path: + The path to the cdb file. + h5_file_path: + The path to the h5 file. + """ + + def __init__( + self, + *, + acp_client: Client, + local_working_directory: Optional[pathlib.Path] = None, + cdb_file_path: Optional[PATH] = None, + acph5_file_path: Optional[PATH] = None, + ): + self._acp_client = acp_client + self._local_working_dir = _LocalWorkingDir(local_working_directory) + self._file_transfer_strategy = _get_file_transfer_strategy( + acp_client=self._acp_client, + local_working_dir=self._local_working_dir, + ) + + if cdb_file_path is not None: + uploaded_file = self._add_input_file(path=pathlib.Path(cdb_file_path)) + if acph5_file_path is None: + self._model = self._acp_client.import_model(path=uploaded_file, format="ansys:cdb") + + if acph5_file_path is not None: + uploaded_file = self._add_input_file(path=pathlib.Path(acph5_file_path)) + self._model = self._acp_client.import_model(path=uploaded_file) + + @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.save_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_acp_h5_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_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/tests/conftest.py b/tests/conftest.py index 16b755cfad..48ae0e099b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ import logging import os import pathlib +import shutil import tempfile from typing import cast @@ -198,7 +199,14 @@ def inner(relative_file_path="minimal_complete_model.acph5", format="acp:h5"): with tempfile.TemporaryDirectory() as tmp_dir: source_path = model_data_dir / relative_file_path client = Client(server=grpc_server) - file_path = client.upload_file(source_path) + + if client.is_remote: + file_path = client.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 client.import_model(path=file_path, format=format) return inner diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 7d9ffc227a..f9b7060672 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -8,6 +8,7 @@ import pyvista from ansys.acp.core import Client, ElementalDataType +from ansys.acp.core._tree_objects._mesh_data import VectorData from .helpers import check_property @@ -56,12 +57,17 @@ def test_unittest(grpc_server, model_data_dir): os.makedirs(working_dir) # model.solver.working_dir = str(working_dir) - save_path = os.path.join(os.path.dirname(remote_path), "test_model_serialization.acph5") - model.save(save_path, save_cache=True) - - client.clear() - - model = client.import_model(path=save_path) + if client.is_remote: + save_path = os.path.join(os.path.dirname(remote_path), "test_model_serialization.acph5") + model.save(save_path, save_cache=True) + client.clear() + model = client.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) + client.clear() + model = client.import_model(path=save_path) # TODO: re-activate these tests when the respective features are implemented # assert model.unit_system.type == "mks" @@ -106,12 +112,18 @@ def test_save_analysis_model(grpc_server, model_data_dir): name="minimal_model", path=remote_file_path, format="ansys:cdb", unit_system="mpa" ) - out_file_path = remote_workdir / "out_file.cdb" - model.save_analysis_model(out_file_path) - with tempfile.TemporaryDirectory() as tmp_dir: - local_file_path = pathlib.Path(tmp_dir, "out_file.cdb") - client.download_file(out_file_path, local_file_path) - assert local_file_path.exists() + if client.is_remote: + out_file_path = remote_workdir / "out_file.cdb" + model.save_analysis_model(out_file_path) + with tempfile.TemporaryDirectory() as tmp_dir: + local_file_path = pathlib.Path(tmp_dir, "out_file.cdb") + client.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.save_analysis_model(local_file_path) + assert local_file_path.exists() def test_string_representation(grpc_server, model_data_dir): @@ -156,21 +168,21 @@ def test_mesh_data(minimal_complete_model): def test_elemental_data(minimal_complete_model): data = minimal_complete_model.elemental_data - numpy.testing.assert_allclose(data.element_labels, np.array([1])) - numpy.testing.assert_allclose(data.normal, np.array([[0.0, 0.0, 1.0]])) - numpy.testing.assert_allclose(data.thickness, np.array([1e-4])) - numpy.testing.assert_allclose(data.relative_thickness_correction, np.array([1.0])) - numpy.testing.assert_allclose(data.area, np.array([9e4])) - numpy.testing.assert_allclose(data.price, np.array([0.0])) - numpy.testing.assert_allclose(data.volume, np.array([9.0])) - numpy.testing.assert_allclose(data.mass, np.array([7.065e-08])) - numpy.testing.assert_allclose(data.offset, np.array([5e-5])) - numpy.testing.assert_allclose(data.cog, np.array([[0.0, 0.0, 5e-5]])) + numpy.testing.assert_allclose(data.element_labels.values, np.array([1])) + numpy.testing.assert_allclose(data.normal.values, np.array([[0.0, 0.0, 1.0]])) + 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.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])) + numpy.testing.assert_allclose(data.cog.values, np.array([[0.0, 0.0, 5e-5]])) def test_nodal_data(minimal_complete_model): data = minimal_complete_model.nodal_data - numpy.testing.assert_allclose(data.node_labels, np.array([1, 2, 3, 4])) + numpy.testing.assert_allclose(data.node_labels.values, np.array([1, 2, 3, 4])) def test_mesh_data_to_pyvista(minimal_complete_model): @@ -182,7 +194,7 @@ def test_mesh_data_to_pyvista(minimal_complete_model): def test_elemental_data_to_pyvista(minimal_complete_model): data = minimal_complete_model.elemental_data - pv_mesh = data.to_pyvista(mesh=minimal_complete_model.mesh) + pv_mesh = data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) assert isinstance(pv_mesh, pyvista.core.pointset.UnstructuredGrid) assert pv_mesh.n_points == 4 assert pv_mesh.n_cells == 1 @@ -193,7 +205,11 @@ def test_elemental_data_to_pyvista_with_component(minimal_complete_model, compon data = minimal_complete_model.elemental_data if not hasattr(data, component): pytest.skip(f"Model elemental data does not contain component '{component}'") - pv_mesh = data.to_pyvista(mesh=minimal_complete_model.mesh, component=component) + component_data = getattr(data, component) + if isinstance(component_data, VectorData): + pv_mesh = component_data.get_pyvista_glyphs(mesh=minimal_complete_model.mesh) + else: + pv_mesh = component_data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) if component in ["normal", "cog"]: assert isinstance(pv_mesh, pyvista.core.pointset.PolyData) else: diff --git a/tests/unittests/test_modeling_ply.py b/tests/unittests/test_modeling_ply.py index 9d4663bd46..e2eae5e2c4 100644 --- a/tests/unittests/test_modeling_ply.py +++ b/tests/unittests/test_modeling_ply.py @@ -12,6 +12,7 @@ SubLaminate, TaperEdge, ) +from ansys.acp.core._tree_objects._mesh_data import VectorData from ansys.acp.core._tree_objects.enums import ( BooleanOperationType, DrapingType, @@ -200,68 +201,68 @@ def simple_modeling_ply(minimal_complete_model): def test_elemental_data(simple_modeling_ply): data = simple_modeling_ply.elemental_data - numpy.testing.assert_allclose(data.element_labels, np.array([1])) - numpy.testing.assert_allclose(data.normal, np.array([[0.0, 0.0, 1.0]])) + numpy.testing.assert_allclose(data.element_labels.values, np.array([1])) + numpy.testing.assert_allclose(data.normal.values, np.array([[0.0, 0.0, 1.0]])) numpy.testing.assert_allclose( - data.orientation, + data.orientation.values, np.array([[0.0, 0.0, 1.0]]), atol=1e-12, ) numpy.testing.assert_allclose( - data.reference_direction, + data.reference_direction.values, np.array([[1.0, 0.0, 0.0]]), atol=1e-12, ) numpy.testing.assert_allclose( - data.fiber_direction, + data.fiber_direction.values, np.array([[1.0, 0.0, 0.0]]), atol=1e-12, ) numpy.testing.assert_allclose( - data.draped_fiber_direction, + data.draped_fiber_direction.values, np.array([[1.0, 0.0, 0.0]]), atol=1e-12, ) numpy.testing.assert_allclose( - data.transverse_direction, + data.transverse_direction.values, np.array([[0.0, 1.0, 0.0]]), atol=1e-12, ) numpy.testing.assert_allclose( - data.draped_transverse_direction, + data.draped_transverse_direction.values, np.array([[0.0, 1.0, 0.0]]), atol=1e-12, ) - numpy.testing.assert_allclose(data.thickness, np.array([1e-4])) - numpy.testing.assert_allclose(data.relative_thickness_correction, np.array([1.0])) + 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.design_angle, np.array([0.0])) - numpy.testing.assert_allclose(data.shear_angle, np.array([0.0])) - numpy.testing.assert_allclose(data.draped_fiber_angle, np.array([0.0])) - numpy.testing.assert_allclose(data.draped_transverse_angle, np.array([90.0])) + numpy.testing.assert_allclose(data.design_angle.values, np.array([0.0])) + numpy.testing.assert_allclose(data.shear_angle.values, np.array([0.0])) + numpy.testing.assert_allclose(data.draped_fiber_angle.values, np.array([0.0])) + numpy.testing.assert_allclose(data.draped_transverse_angle.values, np.array([90.0])) - numpy.testing.assert_allclose(data.area, np.array([9e4])) - numpy.testing.assert_allclose(data.price, np.array([0.0])) - numpy.testing.assert_allclose(data.volume, np.array([9.0])) - numpy.testing.assert_allclose(data.mass, np.array([7.065e-08])) - numpy.testing.assert_allclose(data.offset, np.array([5e-5])) - numpy.testing.assert_allclose(data.cog, np.array([[0.0, 0.0, 5e-5]])) + 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.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])) + numpy.testing.assert_allclose(data.cog.values, np.array([[0.0, 0.0, 5e-5]])) def test_nodal_data(simple_modeling_ply): data = simple_modeling_ply.nodal_data - numpy.testing.assert_allclose(data.node_labels, np.array([1, 2, 3, 4])) + numpy.testing.assert_allclose(data.node_labels.values, np.array([1, 2, 3, 4])) numpy.testing.assert_allclose( - data.ply_offset, + data.ply_offset.values, np.array([[0.0, 0.0, 5e-5], [0.0, 0.0, 5e-5], [0.0, 0.0, 5e-5], [0.0, 0.0, 5e-5]]), ) def test_elemental_data_to_pyvista(minimal_complete_model, simple_modeling_ply): elemental_data = simple_modeling_ply.elemental_data - pv_mesh = elemental_data.to_pyvista(mesh=minimal_complete_model.mesh) + pv_mesh = elemental_data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) assert isinstance(pv_mesh, pyvista.core.pointset.UnstructuredGrid) assert pv_mesh.n_points == 4 assert pv_mesh.n_cells == 1 @@ -274,7 +275,11 @@ def test_elemental_data_to_pyvista_with_component( data = simple_modeling_ply.elemental_data if not hasattr(data, component): pytest.skip(f"Modeling Ply elemental data does not contain component '{component}'") - pv_mesh = data.to_pyvista(mesh=minimal_complete_model.mesh, component=component) + component_data = getattr(data, component) + if isinstance(component_data, VectorData): + pv_mesh = component_data.get_pyvista_glyphs(mesh=minimal_complete_model.mesh, factor=0.01) + else: + pv_mesh = component_data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) if component in [ "normal", "orientation", @@ -298,7 +303,7 @@ def test_elemental_data_to_pyvista_with_component( def test_nodal_data_to_pyvista(minimal_complete_model, simple_modeling_ply): data = simple_modeling_ply.nodal_data - pv_mesh = data.to_pyvista(mesh=minimal_complete_model.mesh) + pv_mesh = data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) assert isinstance(pv_mesh, pyvista.core.pointset.UnstructuredGrid) assert pv_mesh.n_points == 4 assert pv_mesh.n_cells == 1 @@ -311,7 +316,12 @@ def test_nodal_data_to_pyvista_with_component( data = simple_modeling_ply.nodal_data if not hasattr(data, component): pytest.skip(f"Modeling Ply nodal data does not contain component '{component}'") - pv_mesh = data.to_pyvista(mesh=minimal_complete_model.mesh, component=component) + + component_data = getattr(data, component) + if isinstance(component_data, VectorData): + pv_mesh = component_data.get_pyvista_glyphs(mesh=minimal_complete_model.mesh, factor=0.01) + else: + pv_mesh = component_data.get_pyvista_mesh(mesh=minimal_complete_model.mesh) if component in ["ply_offset"]: assert isinstance( pv_mesh, pyvista.core.pointset.PolyData diff --git a/tests/unittests/test_tree_printer.py b/tests/unittests/test_tree_printer.py new file mode 100644 index 0000000000..d957b9e3ae --- /dev/null +++ b/tests/unittests/test_tree_printer.py @@ -0,0 +1,122 @@ +import os + +from ansys.acp.core import Client, get_model_tree + + +def test_printed_model(grpc_server, model_data_dir): + """ + Test that model tree looks correct. + """ + client = Client(server=grpc_server) + + input_file_path = model_data_dir / "minimal_complete_model.acph5" + remote_path = client.upload_file(input_file_path) + + model = client.import_model(name="minimal_complete", path=remote_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 Table + Selection Rules + Oriented Selection Sets + OrientedSelectionSet.1 + Modeling Groups + ModelingGroup.1 + ModelingPly.1 + ProductionPly + P1L1__ModelingPly.1 +""".replace( + "\n", os.linesep + ) + ) + + model.create_edge_set() + model.create_stackup() + model.create_sublaminate() + model.create_virtual_geometry() + model.create_cad_geometry() + model.create_parallel_selection_rule() + model.create_cylindrical_selection_rule() + model.create_tube_selection_rule() + model.create_cutoff_selection_rule() + model.create_geometrical_selection_rule() + model.create_boolean_selection_rule() + model.create_lookup_table_1d() + model.create_lookup_table_3d() + model.create_sensor() + + 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 Table + 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 + ) + ) diff --git a/tests/unittests/test_workflow.py b/tests/unittests/test_workflow.py new file mode 100644 index 0000000000..fe3040f3bd --- /dev/null +++ b/tests/unittests/test_workflow.py @@ -0,0 +1,39 @@ +import pathlib +import tempfile + +import pytest + +from ansys.acp.core import ACPWorkflow, Client + + +@pytest.mark.parametrize("explict_temp_dir", [None, tempfile.TemporaryDirectory()]) +def test_workflow(grpc_server, model_data_dir, explict_temp_dir): + """Test that workflow can be initialized and files can be retrieved.""" + client = Client(server=grpc_server) + 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( + acp_client=client, cdb_file_path=input_file_path, local_working_directory=working_dir + ) + 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_acp_h5_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() diff --git a/type_checks/plots.py b/type_checks/plots.py new file mode 100644 index 0000000000..27e61142dc --- /dev/null +++ b/type_checks/plots.py @@ -0,0 +1,15 @@ +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 + +model = Model() # type: ignore + +modeling_ply = model.modeling_groups["key"].modeling_plies["key"] +assert_type(modeling_ply.elemental_data, ModelingPlyElementalData) +assert_type(modeling_ply.elemental_data.normal, VectorData) +assert_type(modeling_ply.elemental_data.thickness, ScalarData[np.float64]) +assert_type(modeling_ply.elemental_data.element_labels, ScalarData[np.int32]) +assert_type(modeling_ply.nodal_data.node_labels, ScalarData[np.int32]) +assert_type(modeling_ply.nodal_data.ply_offset, VectorData)