Skip to content

Commit 896829b

Browse files
ansbdickenspre-commit-ci[bot]pyansys-ci-botPipKatRobPasMue
authored
feat: Add misc. repair and prepare tool methods (#1293)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Co-authored-by: Umut Soysal <umut.soysal@ansys.com>
1 parent c23b91e commit 896829b

File tree

7 files changed

+204
-9
lines changed

7 files changed

+204
-9
lines changed

doc/changelog.d/1293.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feat: Add misc. repair and prepare tool methods

src/ansys/geometry/core/tools/prepare_tools.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@
2222
"""Provides tools for preparing geometry for use with simulation."""
2323

2424
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
25+
from ansys.api.geometry.v0.models_pb2 import Body as GRPCBody
2526
from ansys.api.geometry.v0.preparetools_pb2 import (
2627
ExtractVolumeFromEdgeLoopsRequest,
2728
ExtractVolumeFromFacesRequest,
29+
ShareTopologyRequest,
2830
)
2931
from ansys.api.geometry.v0.preparetools_pb2_grpc import PrepareToolsStub
3032
from beartype import beartype as check_input_types
3133
from beartype.typing import TYPE_CHECKING, List
34+
from google.protobuf.wrappers_pb2 import BoolValue, DoubleValue
3235

3336
from ansys.geometry.core.connection import GrpcClient
3437
from ansys.geometry.core.errors import protect_grpc
@@ -38,6 +41,7 @@
3841
get_design_from_face,
3942
)
4043
from ansys.geometry.core.misc.checks import min_backend_version
44+
from ansys.geometry.core.typing import Real
4145

4246
if TYPE_CHECKING: # pragma: no cover
4347
from ansys.geometry.core.designer.body import Body
@@ -148,3 +152,37 @@ def extract_volume_from_edge_loops(
148152
else:
149153
self._grpc_client.log.info("Failed to extract volume from edge loops...")
150154
return []
155+
156+
@protect_grpc
157+
@check_input_types
158+
@min_backend_version(25, 1, 0)
159+
def share_topology(
160+
self, bodies: List["Body"], tol: Real = 0.0, preserve_instances: bool = False
161+
) -> bool:
162+
"""Share topology between the chosen bodies.
163+
164+
Parameters
165+
----------
166+
bodies : List[Body]
167+
List of bodies to share topology between.
168+
tol : Real
169+
Maximum distance between bodies.
170+
preserve_instances : bool
171+
Whether instances are preserved.
172+
173+
Returns
174+
-------
175+
bool
176+
``True`` if successful, ``False`` if failed.
177+
"""
178+
if not bodies:
179+
return False
180+
181+
share_topo_response = self._prepare_stub.ShareTopology(
182+
ShareTopologyRequest(
183+
selection=[GRPCBody(id=body.id) for body in bodies],
184+
tolerance=DoubleValue(value=tol),
185+
preserve_instances=BoolValue(value=preserve_instances),
186+
)
187+
)
188+
return share_topo_response.result

src/ansys/geometry/core/tools/problem_areas.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525

2626
from ansys.api.geometry.v0.repairtools_pb2 import (
2727
FixDuplicateFacesRequest,
28+
FixExtraEdgesRequest,
2829
FixInexactEdgesRequest,
2930
FixMissingFacesRequest,
31+
FixShortEdgesRequest,
3032
FixSmallFacesRequest,
3133
FixSplitEdgesRequest,
3234
FixStitchFacesRequest,
@@ -269,9 +271,86 @@ def edges(self) -> List["Edge"]:
269271
"""The list of the ids of the edges connected to this problem area."""
270272
return self._edges
271273

274+
def fix(self) -> RepairToolMessage:
275+
"""Fix the problem area.
276+
277+
Returns
278+
-------
279+
message: RepairToolMessage
280+
Message containing created and/or modified bodies.
281+
"""
282+
if not self.edges:
283+
return RepairToolMessage(False, [], [])
284+
285+
parent_design = get_design_from_edge(self.edges[0])
286+
request = FixExtraEdgesRequest(extra_edge_problem_area_id=self._id_grpc)
287+
response = self._repair_stub.FixExtraEdges(request)
288+
parent_design._update_design_inplace()
289+
message = RepairToolMessage(
290+
response.result.success,
291+
response.result.created_bodies_monikers,
292+
response.result.modified_bodies_monikers,
293+
)
294+
295+
return message
296+
297+
298+
class ShortEdgeProblemAreas(ProblemArea):
299+
"""Represents a short edge problem area with a unique identifier and associated edges.
300+
301+
Parameters
302+
----------
303+
id : str
304+
Server-defined ID for the body.
305+
grpc_client : GrpcClient
306+
Active supporting geometry service instance for design modeling.
307+
edges : List[Edge]
308+
List of edges associated with the design.
309+
"""
310+
311+
def __init__(self, id: str, grpc_client: GrpcClient, edges: List["Edge"]):
312+
"""Initialize a new instance of the ``ShortEdgeProblemAreas`` class."""
313+
super().__init__(id, grpc_client)
314+
315+
from ansys.geometry.core.designer.edge import Edge
316+
317+
# Verify that all elements in the list are edges
318+
check_type_all_elements_in_iterable(edges, Edge)
319+
320+
self._edges = edges
321+
322+
@property
323+
def edges(self) -> List["Edge"]:
324+
"""The list of the ids of the edges connected to this problem area."""
325+
return self._edges
326+
327+
def fix(self) -> RepairToolMessage:
328+
"""Fix the problem area.
329+
330+
Returns
331+
-------
332+
message: RepairToolMessage
333+
Message containing created and/or modified bodies.
334+
"""
335+
if not self.edges:
336+
return RepairToolMessage(False, [], [])
337+
338+
parent_design = get_design_from_edge(self.edges[0])
339+
response = self._repair_stub.FixShortEdges(
340+
FixShortEdgesRequest(short_edge_problem_area_id=self._id_grpc)
341+
)
342+
parent_design._update_design_inplace()
343+
message = RepairToolMessage(
344+
response.result.success,
345+
response.result.created_bodies_monikers,
346+
response.result.modified_bodies_monikers,
347+
)
348+
349+
return message
350+
272351

273352
class SmallFaceProblemAreas(ProblemArea):
274-
"""Represents a small face problem area with unique identifier and associated faces.
353+
"""Represents a small face problem area with a unique identifier and associated faces.
275354
276355
Parameters
277356
----------

src/ansys/geometry/core/tools/repair_tools.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
# SOFTWARE.
2222
"""Provides tools for repairing bodies."""
2323

24+
from ansys.api.geometry.v0.bodies_pb2_grpc import BodiesStub
2425
from ansys.api.geometry.v0.repairtools_pb2 import (
2526
FindDuplicateFacesRequest,
2627
FindExtraEdgesRequest,
2728
FindInexactEdgesRequest,
2829
FindMissingFacesRequest,
30+
FindShortEdgesRequest,
2931
FindSmallFacesRequest,
3032
FindSplitEdgesRequest,
3133
FindStitchFacesRequest,
@@ -46,6 +48,7 @@
4648
ExtraEdgeProblemAreas,
4749
InexactEdgeProblemAreas,
4850
MissingFaceProblemAreas,
51+
ShortEdgeProblemAreas,
4952
SmallFaceProblemAreas,
5053
SplitEdgeProblemAreas,
5154
StitchFaceProblemAreas,
@@ -60,9 +63,10 @@ class RepairTools:
6063
"""Repair tools for PyAnsys Geometry."""
6164

6265
def __init__(self, grpc_client: GrpcClient):
63-
"""Initialize Repair Tools class."""
66+
"""Initialize a new instance of the ``RepairTools`` class."""
6467
self._grpc_client = grpc_client
6568
self._repair_stub = RepairToolsStub(self._grpc_client.channel)
69+
self._bodies_stub = BodiesStub(self._grpc_client.channel)
6670

6771
def find_split_edges(
6872
self, bodies: List["Body"], angle: Real = 0.0, length: Real = 0.0
@@ -101,7 +105,7 @@ def find_split_edges(
101105
parent_design = get_design_from_body(bodies[0])
102106
return [
103107
SplitEdgeProblemAreas(
104-
str(res.id),
108+
f"{res.id}",
105109
self._grpc_client,
106110
get_edges_from_ids(parent_design, res.edge_monikers),
107111
)
@@ -135,7 +139,7 @@ def find_extra_edges(self, bodies: List["Body"]) -> List[ExtraEdgeProblemAreas]:
135139

136140
return [
137141
ExtraEdgeProblemAreas(
138-
str(res.id),
142+
f"{res.id}",
139143
self._grpc_client,
140144
get_edges_from_ids(parent_design, res.edge_monikers),
141145
)
@@ -170,7 +174,45 @@ def find_inexact_edges(self, bodies: List["Body"]) -> List[InexactEdgeProblemAre
170174

171175
return [
172176
InexactEdgeProblemAreas(
173-
str(res.id),
177+
f"{res.id}",
178+
self._grpc_client,
179+
get_edges_from_ids(parent_design, res.edge_monikers),
180+
)
181+
for res in problem_areas_response.result
182+
]
183+
184+
def find_short_edges(
185+
self, bodies: List["Body"], length: Real = 0.0
186+
) -> List[ShortEdgeProblemAreas]:
187+
"""Find the short edge problem areas.
188+
189+
This method finds the short edge problem areas and returns a list of
190+
these objects.
191+
192+
Parameters
193+
----------
194+
bodies : List[Body]
195+
List of bodies that short edges are investigated on.
196+
197+
Returns
198+
-------
199+
List[ShortEdgeProblemAreas]
200+
List of objects representing short edge problem areas.
201+
"""
202+
if not bodies:
203+
return []
204+
205+
problem_areas_response = self._repair_stub.FindShortEdges(
206+
FindShortEdgesRequest(
207+
selection=[body.id for body in bodies],
208+
max_edge_length=DoubleValue(value=length),
209+
)
210+
)
211+
212+
parent_design = get_design_from_body(bodies[0])
213+
return [
214+
ShortEdgeProblemAreas(
215+
f"{res.id}",
174216
self._grpc_client,
175217
get_edges_from_ids(parent_design, res.edge_monikers),
176218
)
@@ -204,7 +246,7 @@ def find_duplicate_faces(self, bodies: List["Body"]) -> List[DuplicateFaceProble
204246
parent_design = get_design_from_body(bodies[0])
205247
return [
206248
DuplicateFaceProblemAreas(
207-
str(res.id),
249+
f"{res.id}",
208250
self._grpc_client,
209251
get_faces_from_ids(parent_design, res.face_monikers),
210252
)
@@ -237,7 +279,7 @@ def find_missing_faces(self, bodies: List["Body"]) -> List[MissingFaceProblemAre
237279

238280
return [
239281
MissingFaceProblemAreas(
240-
str(res.id),
282+
f"{res.id}",
241283
self._grpc_client,
242284
get_edges_from_ids(parent_design, res.edge_monikers),
243285
)
@@ -271,7 +313,7 @@ def find_small_faces(self, bodies: List["Body"]) -> List[SmallFaceProblemAreas]:
271313

272314
return [
273315
SmallFaceProblemAreas(
274-
str(res.id),
316+
f"{res.id}",
275317
self._grpc_client,
276318
get_faces_from_ids(parent_design, res.face_monikers),
277319
)
@@ -301,7 +343,7 @@ def find_stitch_faces(self, bodies: List["Body"]) -> List[StitchFaceProblemAreas
301343
parent_design = get_design_from_body(bodies[0])
302344
return [
303345
StitchFaceProblemAreas(
304-
str(res.id),
346+
f"{res.id}",
305347
self._grpc_client,
306348
get_bodies_from_ids(parent_design, res.body_monikers),
307349
)
20.9 KB
Binary file not shown.

tests/integration/test_prepare_tools.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,11 @@ def test_volume_extract_from_edge_loops(modeler: Modeler):
5757
)
5858

5959
assert len(created_bodies) == 1
60+
61+
62+
def test_share_topology(modeler: Modeler):
63+
"""Test share topology operation is between two bodies."""
64+
skip_if_linux(modeler, test_share_topology.__name__, "prepare_tools") # Skip test on Linux
65+
design = modeler.open_file(FILES_DIR / "MixingTank.scdocx")
66+
67+
assert modeler.prepare_tools.share_topology(design.bodies)

tests/integration/test_repair_tools.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
# SOFTWARE.
2222
""" "Testing of repair tools."""
2323

24+
import pytest
25+
2426
from ansys.geometry.core.modeler import Modeler
2527

2628
from .conftest import FILES_DIR, skip_if_linux
@@ -89,6 +91,15 @@ def test_find_extra_edge_edges(modeler: Modeler):
8991
assert len(problem_areas[0].edges) > 0
9092

9193

94+
@pytest.mark.skip(reason="This test is failing on the Geometry Service - issue 1335")
95+
def test_fix_extra_edge(modeler: Modeler):
96+
"""Test to find and fix extra edge problem areas."""
97+
skip_if_linux(modeler, test_fix_extra_edge.__name__, "repair_tools") # Skip test on Linux
98+
design = modeler.open_file(FILES_DIR / "ExtraEdgesDesignBefore.scdocx")
99+
problem_areas = modeler.repair_tools.find_extra_edges(design.bodies)
100+
assert problem_areas[0].fix().success is True
101+
102+
92103
def test_find_inexact_edges(modeler: Modeler):
93104
"""Test to read geometry and find it's inexact edge problem areas."""
94105
skip_if_linux(modeler, test_find_inexact_edges.__name__, "repair_tools") # Skip test on Linux
@@ -276,3 +287,19 @@ def test_fix_stitch_face(modeler: Modeler):
276287
assert message.success is True
277288
assert len(message.created_bodies) == 0
278289
assert len(message.modified_bodies) > 0
290+
291+
292+
def test_find_short_edges(modeler: Modeler):
293+
"""Test to read geometry and find it's short edge problem areas."""
294+
skip_if_linux(modeler, test_find_short_edges.__name__, "repair_tools") # Skip test on Linux
295+
design = modeler.open_file(FILES_DIR / "ShortEdgesBefore.scdocx")
296+
problem_areas = modeler.repair_tools.find_short_edges(design.bodies, 10)
297+
assert len(problem_areas) == 12
298+
299+
300+
def test_fix_short_edges(modeler: Modeler):
301+
"""Test to read geometry and find and fix it's short edge problem areas."""
302+
skip_if_linux(modeler, test_fix_short_edges.__name__, "repair_tools") # Skip test on Linux
303+
design = modeler.open_file(FILES_DIR / "ShortEdgesBefore.scdocx")
304+
problem_areas = modeler.repair_tools.find_short_edges(design.bodies, 10)
305+
assert problem_areas[0].fix().success is True

0 commit comments

Comments
 (0)