From bf9dda7b6a4e121446a6906fda6a9781ca5177e1 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Fri, 18 Oct 2024 15:14:03 -0500 Subject: [PATCH 01/14] implement chamfer and test --- src/ansys/geometry/core/designer/body.py | 3 + src/ansys/geometry/core/designer/edge.py | 5 ++ src/ansys/geometry/core/modeler.py | 8 +++ src/ansys/geometry/core/tools/__init__.py | 1 + src/ansys/geometry/core/tools/pull_tools.py | 68 +++++++++++++++++++++ tests/integration/test_pull_tools.py | 50 +++++++++++++++ 6 files changed, 135 insertions(+) create mode 100644 src/ansys/geometry/core/tools/pull_tools.py create mode 100644 tests/integration/test_pull_tools.py diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 0db35225c5..0b9e1dbb66 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -1208,6 +1208,9 @@ def wrapper(self: "Body", *args, **kwargs): return wrapper + def _reset_tessellation_cache(self): # noqa: N805 + self._template._tessellation = None + @property def id(self) -> str: # noqa: D102 return self._id diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index d4cd78f4e4..d43ac73235 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -100,6 +100,11 @@ def _grpc_id(self) -> EntityIdentifier: """Entity ID of this edge on the server side.""" return EntityIdentifier(id=self._id) + @property + def body(self) -> "Body": + """Body of the edge.""" + return self._body + @property def is_reversed(self) -> bool: """Flag indicating if the edge is reversed.""" diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index 29a03582f7..45966b15cf 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -42,6 +42,7 @@ from ansys.geometry.core.misc.options import ImportOptions from ansys.geometry.core.tools.measurement_tools import MeasurementTools from ansys.geometry.core.tools.prepare_tools import PrepareTools +from ansys.geometry.core.tools.pull_tools import PullTools from ansys.geometry.core.tools.repair_tools import RepairTools from ansys.geometry.core.typing import Real @@ -128,6 +129,7 @@ def __init__( self._repair_tools = RepairTools(self._grpc_client) self._prepare_tools = PrepareTools(self._grpc_client) self._measurement_tools = MeasurementTools(self._grpc_client) + self._pull_tools = PullTools(self._grpc_client) # Maintaining references to all designs within the modeler workspace self._designs: dict[str, "Design"] = {} @@ -497,3 +499,9 @@ def prepare_tools(self) -> PrepareTools: def measurement_tools(self) -> MeasurementTools: """Access to measurement tools.""" return self._measurement_tools + + @property + @min_backend_version(25, 1, 0) + def pull_tools(self) -> PullTools: + """Access to pull tools.""" + return self._pull_tools diff --git a/src/ansys/geometry/core/tools/__init__.py b/src/ansys/geometry/core/tools/__init__.py index a5da5a3644..7c3e5a5ea8 100644 --- a/src/ansys/geometry/core/tools/__init__.py +++ b/src/ansys/geometry/core/tools/__init__.py @@ -28,5 +28,6 @@ ExtraEdgeProblemAreas, InexactEdgeProblemAreas, ) +from ansys.geometry.core.tools.pull_tools import PullTools from ansys.geometry.core.tools.repair_tool_message import RepairToolMessage from ansys.geometry.core.tools.repair_tools import RepairTools diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py new file mode 100644 index 0000000000..bcd86ea2b3 --- /dev/null +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -0,0 +1,68 @@ +# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Provides tools for measurement.""" + +from typing import TYPE_CHECKING, Union + +from ansys.api.geometry.v0.commands_pb2 import ChamferRequest +from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub +from ansys.geometry.core.connection import GrpcClient +from ansys.geometry.core.errors import protect_grpc +from ansys.geometry.core.misc.checks import min_backend_version +from ansys.geometry.core.typing import Real + +if TYPE_CHECKING: # pragma: no cover + from ansys.geometry.core.designer.edge import Edge + from ansys.geometry.core.designer.face import Face + + +class PullTools: + """Pull tools for PyAnsys Geometry. + + Parameters + ---------- + grpc_client : GrpcClient + gRPC client to use for the measurement tools. + """ + + @protect_grpc + def __init__(self, grpc_client: GrpcClient): + """Initialize pull tools class.""" + self._grpc_client = grpc_client + self._commands_stub = CommandsStub(self._grpc_client.channel) + + @protect_grpc + @min_backend_version(25, 1, 0) + def chamfer(self, edge_or_face: Union["Edge", "Face"], distance: Real): + """Create a chamfer on an edge, or adjust the chamfer of a face. + + Parameters + ---------- + edge_or_face : Edge | Face + Edge or face to act on. + distance : Real + Chamfer distance. + """ + edge_or_face.body._reset_tessellation_cache() + + self._commands_stub.Chamfer(ChamferRequest(id=edge_or_face.id, distance=distance)) + # return all created stuff? diff --git a/tests/integration/test_pull_tools.py b/tests/integration/test_pull_tools.py new file mode 100644 index 0000000000..db7025c23e --- /dev/null +++ b/tests/integration/test_pull_tools.py @@ -0,0 +1,50 @@ +# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" "Testing of repair tools.""" + +from pint import Quantity +import pytest + +from ansys.geometry.core.math.point import Point2D +from ansys.geometry.core.misc import UNITS +from ansys.geometry.core.modeler import Modeler +from ansys.geometry.core.sketch.sketch import Sketch + + +def test_chamfer(modeler: Modeler): + """Test chamfer on edge and face.""" + design = modeler.create_design("chamfer") + + body = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 1, 1), 1) + assert len(body.faces) == 6 + assert len(body.edges) == 12 + assert body.volume.m == pytest.approx(Quantity(1, UNITS.m**3).m, rel=1e-6, abs=1e-8) + + modeler.pull_tools.chamfer(body.edges[0], 0.1) + assert len(body.faces) == 7 + assert len(body.edges) == 15 + assert body.volume.m == pytest.approx(Quantity(0.995, UNITS.m**3).m, rel=1e-6, abs=1e-8) + + modeler.pull_tools.chamfer(body.faces[-1], 0.5) + assert len(body.faces) == 7 + assert len(body.edges) == 15 + assert body.volume.m == pytest.approx(Quantity(0.875, UNITS.m**3).m, rel=1e-6, abs=1e-8) From 5e2710aef06f6c03549df12123413da963b3f311 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:17:15 +0000 Subject: [PATCH 02/14] chore: adding changelog file 1495.added.md [dependabot-skip] --- doc/changelog.d/1495.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/1495.added.md diff --git a/doc/changelog.d/1495.added.md b/doc/changelog.d/1495.added.md new file mode 100644 index 0000000000..0266dfa43d --- /dev/null +++ b/doc/changelog.d/1495.added.md @@ -0,0 +1 @@ +add chamfer tool \ No newline at end of file From 6fcbec1db1a0477c467ab87b532a0631b41de5f7 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Mon, 21 Oct 2024 12:58:19 -0500 Subject: [PATCH 03/14] cleanup --- src/ansys/geometry/core/designer/body.py | 1 + src/ansys/geometry/core/modeler.py | 1 - src/ansys/geometry/core/tools/pull_tools.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 0b9e1dbb66..cd137790a1 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -1209,6 +1209,7 @@ def wrapper(self: "Body", *args, **kwargs): return wrapper def _reset_tessellation_cache(self): # noqa: N805 + """Reset the cached tessellation for a body.""" self._template._tessellation = None @property diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index 45966b15cf..acb0089ec3 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -501,7 +501,6 @@ def measurement_tools(self) -> MeasurementTools: return self._measurement_tools @property - @min_backend_version(25, 1, 0) def pull_tools(self) -> PullTools: """Access to pull tools.""" return self._pull_tools diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index bcd86ea2b3..4bb6bf5663 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -19,7 +19,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Provides tools for measurement.""" +"""Provides tools for pulling geometry.""" from typing import TYPE_CHECKING, Union From 282b43cd0be9b0808a3e6f20777e875574e814f3 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 22 Oct 2024 08:43:35 -0500 Subject: [PATCH 04/14] bump api geometry --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a936710cc7..a1457ab607 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-geometry==0.4.11", + "ansys-api-geometry==0.4.12", "ansys-tools-path>=0.3,<1", "ansys-tools-visualization-interface>=0.2.6,<1", "beartype>=0.11.0,<0.20", From 645c8a88eb55662c7c8ccfcb57b5ff2461b3ae28 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 22 Oct 2024 08:47:04 -0500 Subject: [PATCH 05/14] more cleanup --- src/ansys/geometry/core/designer/body.py | 2 +- src/ansys/geometry/core/tools/pull_tools.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index cd137790a1..0e4e7bca7b 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -1203,7 +1203,7 @@ def reset_tessellation_cache(func): # noqa: N805 @wraps(func) def wrapper(self: "Body", *args, **kwargs): - self._template._tessellation = None + self._reset_tessellation_cache() return func(self, *args, **kwargs) return wrapper diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index 4bb6bf5663..347faa771a 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -52,7 +52,7 @@ def __init__(self, grpc_client: GrpcClient): @protect_grpc @min_backend_version(25, 1, 0) - def chamfer(self, edge_or_face: Union["Edge", "Face"], distance: Real): + def chamfer(self, edge_or_face: Union["Edge", "Face"], distance: Real) -> bool: """Create a chamfer on an edge, or adjust the chamfer of a face. Parameters @@ -61,8 +61,14 @@ def chamfer(self, edge_or_face: Union["Edge", "Face"], distance: Real): Edge or face to act on. distance : Real Chamfer distance. + + Returns + ------- + bool + Success of chamfer command. """ edge_or_face.body._reset_tessellation_cache() - self._commands_stub.Chamfer(ChamferRequest(id=edge_or_face.id, distance=distance)) - # return all created stuff? + result = self._commands_stub.Chamfer(ChamferRequest(id=edge_or_face.id, distance=distance)) + + return result.success From 7b4c94cdc0410dfad7550128feba5d5c7417d9e0 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Wed, 23 Oct 2024 11:50:40 -0500 Subject: [PATCH 06/14] final cleanup, add example --- doc/source/_static/thumbnails/chamfer.png | Bin 0 -> 48695 bytes doc/source/conf.py | 1 + doc/source/examples.rst | 1 + .../examples/03_modeling/chamfer.mystnb | 63 ++++++++++++++++++ src/ansys/geometry/core/tools/pull_tools.py | 19 ++++-- tests/integration/test_pull_tools.py | 13 ++++ 6 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 doc/source/_static/thumbnails/chamfer.png create mode 100644 doc/source/examples/03_modeling/chamfer.mystnb diff --git a/doc/source/_static/thumbnails/chamfer.png b/doc/source/_static/thumbnails/chamfer.png new file mode 100644 index 0000000000000000000000000000000000000000..89a78bc9b4cfd4b82db889298c11e3cabefc1d78 GIT binary patch literal 48695 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

yfe?s>nYrxegt7GziESWA%dO+o2dpw^g6SO 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 F{tpJl-eUj& literal 0 HcmV?d00001 diff --git a/doc/source/conf.py b/doc/source/conf.py index d016751985..57f57410fb 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -293,6 +293,7 @@ def intersphinx_pyansys_geometry(switcher_version: str): "examples/03_modeling/design_tree": "_static/thumbnails/design_tree.png", "examples/03_modeling/service_colors": "_static/thumbnails/service_colors.png", "examples/03_modeling/surface_bodies": "_static/thumbnails/quarter_sphere.png", + "examples/03_modeling/chamfer": "_static/thumbnails/chamfer.png", "examples/04_applied/01_naca_airfoils": "_static/thumbnails/naca_airfoils.png", "examples/04_applied/02_naca_fluent": "_static/thumbnails/naca_fluent.png", } diff --git a/doc/source/examples.rst b/doc/source/examples.rst index 8ec8e76546..1f91c7ca5c 100644 --- a/doc/source/examples.rst +++ b/doc/source/examples.rst @@ -48,6 +48,7 @@ These examples demonstrate service-based modeling operations. examples/03_modeling/design_tree.mystnb examples/03_modeling/service_colors.mystnb examples/03_modeling/surface_bodies.mystnb + examples/03_modeling/chamfer.mystnb Applied examples ---------------- diff --git a/doc/source/examples/03_modeling/chamfer.mystnb b/doc/source/examples/03_modeling/chamfer.mystnb new file mode 100644 index 0000000000..d2d3402277 --- /dev/null +++ b/doc/source/examples/03_modeling/chamfer.mystnb @@ -0,0 +1,63 @@ +--- +jupytext: + text_representation: + extension: .mystnb + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.4 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +# Modeling: Chamfer edges and faces +A chamfer is an angled cut on an edge. Chamfers can be created through the `Modeler.pull_tools` module. + ++++ + +## Create a block +Launch the modeler and create a block. + +```{code-cell} ipython3 +from ansys.geometry.core import launch_modeler, Modeler + +modeler = Modeler() +print(modeler) +``` + +```{code-cell} ipython3 +from ansys.geometry.core.sketch import Sketch +from ansys.geometry.core.math import Point2D + +design = modeler.create_design("chamfer_block") +body = design.extrude_sketch("block", Sketch().box(Point2D([0, 0]), 1, 1), 1) + +body.plot() +``` + +## Chamfer edges +Create a uniform chamfer on all edges of the block. + +```{code-cell} ipython3 +modeler.pull_tools.chamfer(body.edges, distance=0.1) + +body.plot() +``` + +## Chamfer faces +The chamfer of a face can also be modified. Create a chamfer on a single edge, then modify the chamfer distance value by providing the newly created face that represents the chamfer. + +```{code-cell} ipython3 +body = design.extrude_sketch("box", Sketch().box(Point2D([0,0]), 1, 1), 1) + +modeler.pull_tools.chamfer(body.edges[0], distance=0.1) + +body.plot() +``` + +```{code-cell} ipython3 +modeler.pull_tools.chamfer(body.faces[-1], distance=0.3) + +body.plot() +``` diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index 347faa771a..aaf671292c 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -21,7 +21,7 @@ # SOFTWARE. """Provides tools for pulling geometry.""" -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, List, Union from ansys.api.geometry.v0.commands_pb2 import ChamferRequest from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub @@ -52,13 +52,15 @@ def __init__(self, grpc_client: GrpcClient): @protect_grpc @min_backend_version(25, 1, 0) - def chamfer(self, edge_or_face: Union["Edge", "Face"], distance: Real) -> bool: + def chamfer( + self, selection: Union["Edge", List["Edge"], "Face", List["Face"]], distance: Real + ) -> bool: """Create a chamfer on an edge, or adjust the chamfer of a face. Parameters ---------- - edge_or_face : Edge | Face - Edge or face to act on. + edges_or_faces : Edge | List[Edge] | Face | List[Face] + Edge(s) or face(s) to act on. distance : Real Chamfer distance. @@ -67,8 +69,13 @@ def chamfer(self, edge_or_face: Union["Edge", "Face"], distance: Real) -> bool: bool Success of chamfer command. """ - edge_or_face.body._reset_tessellation_cache() + selection = selection if isinstance(selection, list) else [selection] - result = self._commands_stub.Chamfer(ChamferRequest(id=edge_or_face.id, distance=distance)) + for ef in selection: + ef.body._reset_tessellation_cache() + + result = self._commands_stub.Chamfer( + ChamferRequest(ids=[ef.id for ef in selection], distance=distance) + ) return result.success diff --git a/tests/integration/test_pull_tools.py b/tests/integration/test_pull_tools.py index db7025c23e..a08c55ea60 100644 --- a/tests/integration/test_pull_tools.py +++ b/tests/integration/test_pull_tools.py @@ -48,3 +48,16 @@ def test_chamfer(modeler: Modeler): assert len(body.faces) == 7 assert len(body.edges) == 15 assert body.volume.m == pytest.approx(Quantity(0.875, UNITS.m**3).m, rel=1e-6, abs=1e-8) + + # multiple edges + body2 = design.extrude_sketch("box2", Sketch().box(Point2D([0, 0]), 1, 1), 1) + assert len(body2.faces) == 6 + assert len(body2.edges) == 12 + assert body2.volume.m == pytest.approx(Quantity(1, UNITS.m**3).m, rel=1e-6, abs=1e-8) + + modeler.pull_tools.chamfer(body2.edges, 0.1) + assert len(body2.faces) == 26 + assert len(body2.edges) == 48 + assert body2.volume.m == pytest.approx( + Quantity(0.945333333333333333, UNITS.m**3).m, rel=1e-6, abs=1e-8 + ) From 22d114c51a9b3725c502c98ad8cab4311732c960 Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:58:18 -0500 Subject: [PATCH 07/14] Update doc/source/examples/03_modeling/chamfer.mystnb Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- doc/source/examples/03_modeling/chamfer.mystnb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/03_modeling/chamfer.mystnb b/doc/source/examples/03_modeling/chamfer.mystnb index d2d3402277..d86d55547b 100644 --- a/doc/source/examples/03_modeling/chamfer.mystnb +++ b/doc/source/examples/03_modeling/chamfer.mystnb @@ -12,7 +12,7 @@ kernelspec: --- # Modeling: Chamfer edges and faces -A chamfer is an angled cut on an edge. Chamfers can be created through the `Modeler.pull_tools` module. +A chamfer is an angled cut on an edge. Chamfers can be created using the ``Modeler.pull_tools`` module. +++ From 7ea2daaff7d86e8aa4678731fe8562ac4e0d473e Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:58:26 -0500 Subject: [PATCH 08/14] Update doc/source/examples/03_modeling/chamfer.mystnb Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- doc/source/examples/03_modeling/chamfer.mystnb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/03_modeling/chamfer.mystnb b/doc/source/examples/03_modeling/chamfer.mystnb index d86d55547b..eeddcfb27f 100644 --- a/doc/source/examples/03_modeling/chamfer.mystnb +++ b/doc/source/examples/03_modeling/chamfer.mystnb @@ -46,7 +46,7 @@ body.plot() ``` ## Chamfer faces -The chamfer of a face can also be modified. Create a chamfer on a single edge, then modify the chamfer distance value by providing the newly created face that represents the chamfer. +The chamfer of a face can also be modified. Create a chamfer on a single edge and then modify the chamfer distance value by providing the newly created face that represents the chamfer. ```{code-cell} ipython3 body = design.extrude_sketch("box", Sketch().box(Point2D([0,0]), 1, 1), 1) From ac4b9d12e43d7b7f31864fca75c3cbab4e757960 Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:58:32 -0500 Subject: [PATCH 09/14] Update src/ansys/geometry/core/tools/pull_tools.py Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- src/ansys/geometry/core/tools/pull_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index aaf671292c..3c921c7305 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -36,7 +36,7 @@ class PullTools: - """Pull tools for PyAnsys Geometry. + """Provides pull tools for PyAnsys Geometry. Parameters ---------- From 4205a173f2f660e538a5c7c8cf6d592d4aa6b3a5 Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:58:39 -0500 Subject: [PATCH 10/14] Update src/ansys/geometry/core/tools/pull_tools.py Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- src/ansys/geometry/core/tools/pull_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index 3c921c7305..215bf41852 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -46,7 +46,7 @@ class PullTools: @protect_grpc def __init__(self, grpc_client: GrpcClient): - """Initialize pull tools class.""" + """Initialize an instance of the ``PullTools`` class.""" self._grpc_client = grpc_client self._commands_stub = CommandsStub(self._grpc_client.channel) From 8786d5590dc4d6d557fa0b1df4a728dfb3d8b346 Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:58:47 -0500 Subject: [PATCH 11/14] Update src/ansys/geometry/core/tools/pull_tools.py Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- src/ansys/geometry/core/tools/pull_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index 215bf41852..c8aa180421 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -60,7 +60,7 @@ def chamfer( Parameters ---------- edges_or_faces : Edge | List[Edge] | Face | List[Face] - Edge(s) or face(s) to act on. + One or more edges or faces to act on. distance : Real Chamfer distance. From 590560691d421eb2906f9314f85b8deed3c4988a Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:58:55 -0500 Subject: [PATCH 12/14] Update src/ansys/geometry/core/tools/pull_tools.py Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- src/ansys/geometry/core/tools/pull_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index c8aa180421..9761ceaa8f 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -67,7 +67,7 @@ def chamfer( Returns ------- bool - Success of chamfer command. + ``True`` when successful, ``False`` when failed. """ selection = selection if isinstance(selection, list) else [selection] From dd34aaeca3f9700f6e671d7db37cdadd1b6bc648 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:41:34 +0100 Subject: [PATCH 13/14] fix: various typehint issues and proper ID --- doc/source/examples/03_modeling/boolean_operations.mystnb | 2 +- doc/source/examples/04_applied/01_naca_airfoils.mystnb | 4 ++-- src/ansys/geometry/core/modeler.py | 2 +- src/ansys/geometry/core/tools/pull_tools.py | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/source/examples/03_modeling/boolean_operations.mystnb b/doc/source/examples/03_modeling/boolean_operations.mystnb index 26727f9005..6f17137450 100644 --- a/doc/source/examples/03_modeling/boolean_operations.mystnb +++ b/doc/source/examples/03_modeling/boolean_operations.mystnb @@ -91,7 +91,7 @@ output list is sorted according to the picking order. pl = GeometryPlotter(allow_picking=True) pl.plot(design.bodies) pl.show() -bodies: List[Body] = GeometryPlotter(allow_picking=True).show(design.bodies) +bodies: list[Body] = GeometryPlotter(allow_picking=True).show(design.bodies) ``` Otherwise, you can select bodies from the design directly. diff --git a/doc/source/examples/04_applied/01_naca_airfoils.mystnb b/doc/source/examples/04_applied/01_naca_airfoils.mystnb index 87d74e195a..df34995454 100644 --- a/doc/source/examples/04_applied/01_naca_airfoils.mystnb +++ b/doc/source/examples/04_applied/01_naca_airfoils.mystnb @@ -43,7 +43,7 @@ import numpy as np from ansys.geometry.core.math import Point2D -def naca_airfoil_4digits(number: Union[int, str], n_points: int = 200) -> List[Point2D]: +def naca_airfoil_4digits(number: Union[int, str], n_points: int = 200) -> list[Point2D]: """ Generate a NACA 4-digits airfoil. @@ -58,7 +58,7 @@ def naca_airfoil_4digits(number: Union[int, str], n_points: int = 200) -> List[P Returns ------- - List[Point2D] + list[Point2D] List of points that define the airfoil. """ # Check if the number is a string diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index 07f4f6d50a..61bae7c17a 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -60,7 +60,7 @@ class Modeler: ---------- host : str, default: DEFAULT_HOST Host where the server is running. - port : Union[str, int], default: DEFAULT_PORT + port : str | int, default: DEFAULT_PORT Port number where the server is running. channel : ~grpc.Channel, default: None gRPC channel for server communication. diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index 9761ceaa8f..0eeb202ac3 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -59,7 +59,7 @@ def chamfer( Parameters ---------- - edges_or_faces : Edge | List[Edge] | Face | List[Face] + edges_or_faces : Edge | list[Edge] | Face | list[Face] One or more edges or faces to act on. distance : Real Chamfer distance. @@ -69,13 +69,13 @@ def chamfer( bool ``True`` when successful, ``False`` when failed. """ - selection = selection if isinstance(selection, list) else [selection] + selection: list[Edge | Face] = selection if isinstance(selection, list) else [selection] for ef in selection: ef.body._reset_tessellation_cache() result = self._commands_stub.Chamfer( - ChamferRequest(ids=[ef.id for ef in selection], distance=distance) + ChamferRequest(ids=[ef._grpc_id for ef in selection], distance=distance) ) return result.success From af23732c43aaa4f6ceec2209bb37ba4b3d5bed94 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:38:36 +0100 Subject: [PATCH 14/14] Update src/ansys/geometry/core/tools/pull_tools.py --- src/ansys/geometry/core/tools/pull_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/tools/pull_tools.py b/src/ansys/geometry/core/tools/pull_tools.py index 0eeb202ac3..58dd105d05 100644 --- a/src/ansys/geometry/core/tools/pull_tools.py +++ b/src/ansys/geometry/core/tools/pull_tools.py @@ -51,7 +51,7 @@ def __init__(self, grpc_client: GrpcClient): self._commands_stub = CommandsStub(self._grpc_client.channel) @protect_grpc - @min_backend_version(25, 1, 0) + @min_backend_version(25, 2, 0) def chamfer( self, selection: Union["Edge", List["Edge"], "Face", List["Face"]], distance: Real ) -> bool: