Repository: ansys/pyansys-geometry
Pull request #1851: feat: Remove rounds
Author: login: smereu
Labels: name: enhancement
description: New features or code improvements
Created: 2025-03-19T14:29:51Z | Merged: 2025-03-20T09:54:34Z

## Description
Exposes the ability to remove rounds. The round faces to be removed are input to the method.  The functionality was available in previous versions of the API server but CoreServices probably did not implement it until recently. I am using min version 25, 2 decorator but I can change it as needed.

## Issue linked
**Please mention the issue number or describe the problem this pull request addresses.**

## Checklist
- [X] I have tested my changes locally.
- [ ] I have added necessary documentation or updated existing documentation.
- [X] I have followed the coding style guidelines of this project.
- [X] I have added appropriate unit tests.
- [X] I have reviewed my changes before submitting this pull request.
- [ ] I have linked the issue or issues that are solved to the PR if any.
- [X] I have assigned this PR to myself.
- [X] I have added the minimum version decorator to any new backend method implemented.
- [X] I have made sure that the title of my PR follows [Conventional commits style](https://www.conventionalcommits.org/en/v1.0.0/#summary) (e.g. ``feat: extrude circle to cylinder``)



Diff:
diff --git a/doc/changelog.d/1851.added.md b/doc/changelog.d/1851.added.md
new file mode 100644
index 0000000000..0fd2c1b77a
--- /dev/null
+++ b/doc/changelog.d/1851.added.md
@@ -0,0 +1 @@
+Remove rounds
\ No newline at end of file
diff --git a/src/ansys/geometry/core/tools/prepare_tools.py b/src/ansys/geometry/core/tools/prepare_tools.py
index 1723533a81..e0b9d4ca48 100644
--- a/src/ansys/geometry/core/tools/prepare_tools.py
+++ b/src/ansys/geometry/core/tools/prepare_tools.py
@@ -26,10 +26,11 @@
 from google.protobuf.wrappers_pb2 import BoolValue, DoubleValue
 
 from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
-from ansys.api.geometry.v0.models_pb2 import Body as GRPCBody
+from ansys.api.geometry.v0.models_pb2 import Body as GRPCBody, Face as GRPCFace
 from ansys.api.geometry.v0.preparetools_pb2 import (
     ExtractVolumeFromEdgeLoopsRequest,
     ExtractVolumeFromFacesRequest,
+    RemoveRoundsRequest,
     ShareTopologyRequest,
 )
 from ansys.api.geometry.v0.preparetools_pb2_grpc import PrepareToolsStub
@@ -168,6 +169,49 @@ def extract_volume_from_edge_loops(
             self._grpc_client.log.info("Failed to extract volume from edge loops...")
             return []
 
+    @protect_grpc
+    def remove_rounds(self, faces: list["Face"], auto_shrink: bool = False) -> bool:
+        """Remove rounds from geometry.
+
+        Tries to remove rounds from geometry. Faces to be removed are input to the method.
+
+        Parameters
+        ----------
+        round_faces : list[Face]
+            List of rounds faces to be removed
+        auto_shrink : bool, default: False
+            Whether to shrink the geometry after removing rounds. Fills in the gaps
+            left by the removed rounds.
+
+        Returns
+        -------
+        bool
+            ``True`` if successful, ``False`` if failed.
+        """
+        from ansys.geometry.core.designer.face import Face
+
+        if not faces:
+            self._grpc_client.log.info("No faces provided...")
+            return []
+
+        # Verify inputs
+        check_type_all_elements_in_iterable(faces, Face)
+
+        parent_design = get_design_from_face(faces[0])
+        response = self._prepare_stub.RemoveRounds(
+            RemoveRoundsRequest(
+                selection=[GRPCFace(id=face.id) for face in faces],
+                auto_shrink=BoolValue(value=auto_shrink),
+            )
+        )
+
+        if response.result:
+            parent_design._update_design_inplace()
+        else:
+            self._grpc_client.log.info("Failed to remove rounds...")
+
+        return response.result
+
     @protect_grpc
     @min_backend_version(24, 2, 0)
     def share_topology(
diff --git a/tests/integration/files/BoxWithRound.scdocx b/tests/integration/files/BoxWithRound.scdocx
new file mode 100644
index 0000000000..f91802c8cd
Binary files /dev/null and b/tests/integration/files/BoxWithRound.scdocx differ
diff --git a/tests/integration/test_prepare_tools.py b/tests/integration/test_prepare_tools.py
index f3ac79040b..80bae656a5 100644
--- a/tests/integration/test_prepare_tools.py
+++ b/tests/integration/test_prepare_tools.py
@@ -56,6 +56,22 @@ def test_volume_extract_from_edge_loops(modeler: Modeler):
     assert len(created_bodies) == 1
 
 
+def test_remove_rounds(modeler: Modeler):
+    """Test a round is removed from the geometry."""
+    from ansys.geometry.core.designer import SurfaceType
+
+    design = modeler.open_file(FILES_DIR / "BoxWithRound.scdocx")
+    assert len(design.bodies[0].faces) == 7
+    roundfaces = [
+        face
+        for face in design.bodies[0].faces
+        if face.surface_type == SurfaceType.SURFACETYPE_CYLINDER
+    ]
+    result = modeler.prepare_tools.remove_rounds(roundfaces)
+    assert len(design.bodies[0].faces) == 6
+    assert result is True
+
+
 def test_share_topology(modeler: Modeler):
     """Test share topology operation is between two bodies."""
     design = Modeler.create_design(modeler, "ShareTopoDoc")


Reviews:
user: login: RobPasMue
body: Implementation looks perfect. Just left some potential enhancements. I see the tests are failing but once that's in we should be good to merge! Thanks @smereu 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-19T16:18:06Z
user: login: umutsoysalansys
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T16:33:43Z
user: login: RobPasMue
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T19:33:02Z
user: login: b-matteo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T09:36:43Z

Review comments:
diff_hunk: @@ -168,6 +169,37 @@ def extract_volume_from_edge_loops(
             self._grpc_client.log.info("Failed to extract volume from edge loops...")
             return []
 
+    @protect_grpc
+    @min_backend_version(25, 2, 0)
+    def remove_rounds(self, faces: list["Face"]):
+        """Remove rounds from geometry.
+
+        Tries to remove rounds from geometry.  Faces to be removed are input to the method.
+
+        Parameters
+        ----------
+        round_faces : list[Face]
+            List of rounds faces to be removed
+
+        """
+        from ansys.geometry.core.designer.face import Face
+
+        if not faces:
+            self._grpc_client.log.info("No faces provided...")
+            return []
+
+        # Verify inputs
+        check_type_all_elements_in_iterable(faces, Face)
+
+        parent_design = get_design_from_face(faces[0])
+
+        response = self._prepare_stub.RemoveRounds(RemoveRoundsRequest(faces=faces))
+
+        if response.result:
+            parent_design._update_design_inplace()
+        else:
+            self._grpc_client.log.info("Failed to remove rounds...")
path: src/ansys/geometry/core/tools/prepare_tools.py
user: login: RobPasMue
body: Should we return the response status (i.e. boolean)? I think other tools are doing so.
created_at: 2025-03-19T16:16:53Z
author_association: MEMBER
diff_hunk: @@ -168,6 +169,37 @@ def extract_volume_from_edge_loops(
             self._grpc_client.log.info("Failed to extract volume from edge loops...")
             return []
 
+    @protect_grpc
+    @min_backend_version(25, 2, 0)
+    def remove_rounds(self, faces: list["Face"]):
path: src/ansys/geometry/core/tools/prepare_tools.py
user: login: RobPasMue
body: Whether we return the response status (boolean) or nothing (None) let's add a typehint. And adapt the docstring if we return the boolean.
created_at: 2025-03-19T16:17:29Z
author_association: MEMBER
diff_hunk: @@ -56,6 +56,21 @@ def test_volume_extract_from_edge_loops(modeler: Modeler):
     assert len(created_bodies) == 1
 
 
+def test_remove_rounds(modeler: Modeler):
+    """Test a round is removed from the geometry."""
+    from ansys.geometry.core.designer import SurfaceType
+
+    design = modeler.open_file(r"C:\temp\BoxWithRound.scdocx")
path: tests/integration/test_prepare_tools.py
user: login: umutsoysalansys
body: @smereu looks like file path needs to be updated. 
created_at: 2025-03-19T16:32:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -56,6 +56,21 @@ def test_volume_extract_from_edge_loops(modeler: Modeler):
     assert len(created_bodies) == 1
 
 
+def test_remove_rounds(modeler: Modeler):
+    """Test a round is removed from the geometry."""
+    from ansys.geometry.core.designer import SurfaceType
+
+    design = modeler.open_file(r"C:\temp\BoxWithRound.scdocx")
path: tests/integration/test_prepare_tools.py
user: login: RobPasMue
body: Very good catch @umutsoysalansys!! I completely missed that one
created_at: 2025-03-19T19:33:01Z
author_association: MEMBER

PR comments:
user: login: RobPasMue
created_at: 2025-03-20T09:02:54Z
body: @smereu - tests were failing because the proto API was expecting "selection" and not "faces". I just modified it and committed the change. Tests should pass after this change I guess.

https://github.com/ansys/pyansys-geometry/pull/1851/commits/17c1acc6821fde28d75b723283ce256d43f39a72

BTW - which is the service version in which this API was implemented? Since CoreService is not available earlier than 25R2, it is not a problem. We can probably add the first version in which this API was added
author_association: MEMBER
user: login: RobPasMue
created_at: 2025-03-20T09:09:26Z
body: Okay so it seems that the API was available as of 24.1 (just checked with @b-matteo). So we do not need to add the decorator in that case. I will take over a few changes in this PR to align it to other prepare tools (typehints & docstrings). I want to get it in before we merge blitz into main - and that should happen today.
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-03-20T09:52:01Z
body: ## [Codecov](https://app.codecov.io/gh/ansys/pyansys-geometry/pull/1851?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) Report
Attention: Patch coverage is `78.57143%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 90.10%. Comparing base [(`371d89a`)](https://app.codecov.io/gh/ansys/pyansys-geometry/commit/371d89a09aa3dffa7c26c8df7ff64ecb036bb6fa?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) to head [(`4886ea5`)](https://app.codecov.io/gh/ansys/pyansys-geometry/commit/4886ea5b38dfff60c65f9c2d350b6e2419b3fee9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys).
> Report is 1 commits behind head on blitz.

| [Files with missing lines](https://app.codecov.io/gh/ansys/pyansys-geometry/pull/1851?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) | Patch % | Lines |
|---|---|---|
| [src/ansys/geometry/core/tools/prepare\_tools.py](https://app.codecov.io/gh/ansys/pyansys-geometry/pull/1851?src=pr&el=tree&filepath=src%2Fansys%2Fgeometry%2Fcore%2Ftools%2Fprepare_tools.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys#diff-c3JjL2Fuc3lzL2dlb21ldHJ5L2NvcmUvdG9vbHMvcHJlcGFyZV90b29scy5weQ==) | 78.57% | [3 Missing :warning: ](https://app.codecov.io/gh/ansys/pyansys-geometry/pull/1851?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##            blitz    #1851      +/-   ##
==========================================
- Coverage   90.12%   90.10%   -0.02%     
==========================================
  Files          92       92              
  Lines        8810     8823      +13     
==========================================
+ Hits         7940     7950      +10     
- Misses        870      873       +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ansys/pyansys-geometry/pull/1851?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE