Skip to content

feat: Implementation of inspect & repair geometry #1712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c802518
Implementation of inspect & repair geometry
smereu Jan 31, 2025
7798489
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 31, 2025
1d8fe34
style changes
smereu Jan 31, 2025
049e52d
Merge branch 'feat/inspect_repair_results' of https://github.com/ansy…
smereu Jan 31, 2025
292b068
clean-up & add test
smereu Feb 3, 2025
7249ef0
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Feb 3, 2025
697d325
chore: adding changelog file 1712.added.md [dependabot-skip]
pyansys-ci-bot Feb 4, 2025
76536a3
clean-up
smereu Feb 4, 2025
7186e11
fix for pre-commit check
smereu Feb 4, 2025
c6aaf23
Merge branch 'feat/inspect_repair_results' of https://github.com/ansy…
smereu Feb 4, 2025
a332540
Merge branch 'main' into feat/inspect_repair_results
smereu Feb 5, 2025
6055fd1
Respond to code review
smereu Feb 5, 2025
718f6f7
Update test_repair_tools.py
smereu Feb 5, 2025
e321eb4
Update src/ansys/geometry/core/tools/repair_tools.py
RobPasMue Feb 6, 2025
f029178
fix: pr review enhancements
RobPasMue Feb 6, 2025
e79361c
Merge branch 'main' into feat/inspect_repair_results
RobPasMue Feb 6, 2025
3d34585
fix: should be using grpc_id for server request
RobPasMue Feb 6, 2025
47430a1
fix: should be using grpc_id for server request
RobPasMue Feb 6, 2025
05cad01
Merge branch 'main' into feat/inspect_repair_results
RobPasMue Feb 7, 2025
644978c
adjust test for inspect geometry
smereu Feb 11, 2025
663d0db
Merge branch 'feat/inspect_repair_results' of https://github.com/ansy…
smereu Feb 11, 2025
f1d39c5
remove duplicated lines after merge
smereu Feb 11, 2025
954a8f8
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Feb 11, 2025
0ebdae9
Merge branch 'main' into feat/inspect_repair_results
RobPasMue Feb 11, 2025
97b9876
fix corresponding to Disco default
smereu Feb 11, 2025
08dc2c6
Merge branch 'main' into feat/inspect_repair_results
RobPasMue Feb 12, 2025
00e83e1
Merge branch 'feat/inspect_repair_results' of https://github.com/ansy…
smereu Feb 12, 2025
694d44f
Merge branch 'main' into feat/inspect_repair_results
RobPasMue Feb 13, 2025
8338909
Merge branch 'main' into feat/inspect_repair_results
RobPasMue Feb 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/1712.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implementation of inspect & repair geometry
2 changes: 1 addition & 1 deletion src/ansys/geometry/core/modeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def __init__(
self._measurement_tools = MeasurementTools(self._grpc_client)

# Enabling tools/commands for all: repair and prepare tools, geometry commands
self._repair_tools = RepairTools(self._grpc_client)
self._repair_tools = RepairTools(self._grpc_client, self)
self._prepare_tools = PrepareTools(self._grpc_client)
self._geometry_commands = GeometryCommands(self._grpc_client)
self._unsupported = UnsupportedCommands(self._grpc_client, self)
Expand Down
135 changes: 135 additions & 0 deletions src/ansys/geometry/core/tools/check_geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Copyright (C) 2023 - 2025 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.
"""Module for repair tool message."""

from typing import TYPE_CHECKING

from ansys.api.geometry.v0.repairtools_pb2 import RepairGeometryRequest
from ansys.api.geometry.v0.repairtools_pb2_grpc import RepairToolsStub
from ansys.geometry.core.connection.client import GrpcClient
from ansys.geometry.core.tools.repair_tool_message import RepairToolMessage

if TYPE_CHECKING: # pragma: no cover
from ansys.geometry.core.designer.body import Body


class GeometryIssue:
"""Provides return message for the repair tool methods."""

def __init__(
self,
message_type: str,
message_id: str,
message: str,
edges: list[str],
faces: list[str],
):
"""Initialize a new instance of a geometry issue found during geometry inspect.

Parameters
----------
message_type: str
Type of the message (warning, error, info).
message_id: str
Identifier for the message.
message
Message that describes the geometry issue.
edges: list[str]
List of edges (if any) that are part of the issue.
modified_bodies: list[str]
List of faces that are part of the issue.
"""
self._message_type = message_type
self._message_id = message_id
self._message = message
self._edges = edges
self._faces = faces

@property
def message_type(self) -> str:
"""The type of the message (warning, error, info)."""
return self._message_type

@property
def message_id(self) -> str:
"""The identifier for the message."""
return self._message_id

@property
def message(self) -> str:
"""The content of the message."""
return self._message

@property
def edges(self) -> list[str]:
"""The List of edges (if any) that are part of the issue."""
return self._edges

@property
def faces(self) -> list[str]:
"""The List of faces (if any) that are part of the issue."""
return self._faces


class InspectResult:
"""Provides the result of the inspect geometry operation."""

def __init__(self, grpc_client: GrpcClient, body: "Body", issues: list[GeometryIssue]):
"""Initialize a new instance of the result of the inspect geometry operation.

Parameters
----------
body: Body
Body for which issues are found.
issues: list[GeometryIssue]
List of issues for the body.
"""
self._body = body
self._issues = issues
self._repair_stub = RepairToolsStub(grpc_client.channel)

@property
def body(self) -> "Body":
"""The body for which issues are found."""
return self._body

@property
def issues(self) -> list[GeometryIssue]:
"""The list of issues for the body."""
return self._issues

def repair(self) -> RepairToolMessage:
"""Repair the problem area.

Returns
-------
RepairToolMessage
Message containing created and/or modified bodies.
"""
if not self.body:
return RepairToolMessage(False, [], [])

repair_result_response = self._repair_stub.RepairGeometry(
RepairGeometryRequest(bodies=[self.body._grpc_id])
)

return RepairToolMessage(repair_result_response.result.success, [], [])
101 changes: 100 additions & 1 deletion src/ansys/geometry/core/tools/repair_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
from google.protobuf.wrappers_pb2 import BoolValue, DoubleValue

from ansys.api.geometry.v0.bodies_pb2_grpc import BodiesStub
from ansys.api.geometry.v0.models_pb2 import (
InspectGeometryMessageId,
InspectGeometryMessageType,
InspectGeometryResult,
InspectGeometryResultIssue,
)
from ansys.api.geometry.v0.repairtools_pb2 import (
FindAdjustSimplifyRequest,
FindDuplicateFacesRequest,
Expand All @@ -37,6 +43,8 @@
FindSmallFacesRequest,
FindSplitEdgesRequest,
FindStitchFacesRequest,
InspectGeometryRequest,
RepairGeometryRequest,
)
from ansys.api.geometry.v0.repairtools_pb2_grpc import RepairToolsStub
from ansys.geometry.core.connection import GrpcClient
Expand All @@ -52,6 +60,7 @@
check_type_all_elements_in_iterable,
min_backend_version,
)
from ansys.geometry.core.tools.check_geometry import GeometryIssue, InspectResult
from ansys.geometry.core.tools.problem_areas import (
DuplicateFaceProblemAreas,
ExtraEdgeProblemAreas,
Expand All @@ -69,16 +78,18 @@

if TYPE_CHECKING: # pragma: no cover
from ansys.geometry.core.designer.body import Body
from ansys.geometry.core.modeler import Modeler


class RepairTools:
"""Repair tools for PyAnsys Geometry."""

def __init__(self, grpc_client: GrpcClient):
def __init__(self, grpc_client: GrpcClient, modeler: "Modeler"):
"""Initialize a new instance of the ``RepairTools`` class."""
self._grpc_client = grpc_client
self._repair_stub = RepairToolsStub(self._grpc_client.channel)
self._bodies_stub = BodiesStub(self._grpc_client.channel)
self._modeler = modeler

@protect_grpc
def find_split_edges(
Expand Down Expand Up @@ -598,3 +609,91 @@ def find_and_fix_split_edges(
response.modified_bodies_monikers,
)
return message

@protect_grpc
@min_backend_version(25, 2, 0)
def inspect_geometry(self, bodies: list["Body"] = None) -> list[InspectResult]:
"""Return a list of geometry issues organized by body.

This method inspects the geometry and returns a list of the issues grouped by
the body where they are found.

Parameters
----------
bodies : list[Body]
List of bodies to inspect the geometry for.
All bodies are inspected if the argument is not given.

Returns
-------
list[IssuesByBody]
List of objects representing geometry issues and the bodies where issues are found.
"""
parent_design = self._modeler.get_active_design()
body_ids = [] if bodies is None else [body._grpc_id for body in bodies]
inspect_result_response = self._repair_stub.InspectGeometry(
InspectGeometryRequest(bodies=body_ids)
)
return self.__create_inspect_result_from_response(
parent_design, inspect_result_response.issues_by_body
)

def __create_inspect_result_from_response(
self, design, inspect_geometry_results: list[InspectGeometryResult]
) -> list[InspectResult]:
inspect_results = []
for inspect_geometry_result in inspect_geometry_results:
body = get_bodies_from_ids(design, [inspect_geometry_result.body.id])
issues = self.__create_issues_from_response(inspect_geometry_result.issues)
inspect_result = InspectResult(
grpc_client=self._grpc_client, body=body[0], issues=issues
)
inspect_results.append(inspect_result)

return inspect_results

def __create_issues_from_response(
self,
inspect_geometry_result_issues: list[InspectGeometryResultIssue],
) -> list[GeometryIssue]:
issues = []
for inspect_result_issue in inspect_geometry_result_issues:
message_type = InspectGeometryMessageType.Name(inspect_result_issue.message_type)
message_id = InspectGeometryMessageId.Name(inspect_result_issue.message_id)
message = inspect_result_issue.message

issue = GeometryIssue(
message_type=message_type,
message_id=message_id,
message=message,
faces=[face.id for face in inspect_result_issue.faces],
edges=[edge.id for edge in inspect_result_issue.edges],
)
issues.append(issue)
return issues

@protect_grpc
@min_backend_version(25, 2, 0)
def repair_geometry(self, bodies: list["Body"] = None) -> RepairToolMessage:
"""Attempt to repair the geometry for the given bodies.

This method inspects the geometry for the given bodies and attempts to repair them.

Parameters
----------
bodies : list[Body]
List of bodies where to attempt to repair the geometry.
All bodies are repaired if the argument is not given.

Returns
-------
RepairToolMessage
Message containing success of the operation.
"""
body_ids = [] if bodies is None else [body._grpc_id for body in bodies]
repair_result_response = self._repair_stub.RepairGeometry(
RepairGeometryRequest(bodies=body_ids)
)

message = RepairToolMessage(repair_result_response.result.success, [], [])
return message
Binary file not shown.
25 changes: 25 additions & 0 deletions tests/integration/test_repair_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,28 @@ def test_find_and_fix_extra_edges(modeler: Modeler):
for body in design.bodies:
final_edge_count += len(body.edges)
assert final_edge_count == 36


def test_inspect_geometry(modeler: Modeler):
"""Test the result of the inspect geometry query and the ability to repair one issue"""
modeler.open_file(FILES_DIR / "InspectAndRepair01.scdocx")
inspect_results = modeler.repair_tools.inspect_geometry()
assert len(inspect_results) == 1
issues = len(inspect_results[0].issues)
assert issues == 7
result_to_repair = inspect_results[0]
result_to_repair.repair()
# Reinspect the geometry
inspect_results = modeler.repair_tools.inspect_geometry()
# All issues should have been fixed
assert len(inspect_results) == 0


def test_repair_geometry(modeler: Modeler):
"""Test the ability to repair a geometry. Inspect geometry is called behind the scenes"""
modeler.open_file(FILES_DIR / "InspectAndRepair01.scdocx")
modeler.repair_tools.repair_geometry()
# Reinspect the geometry
inspect_results = modeler.repair_tools.inspect_geometry()
# All issues should have been fixed
assert len(inspect_results) == 0
Loading