From 084fef894baa2ae339ef0c92d19b43d3387a2122 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Wed, 14 May 2025 13:31:35 +0200 Subject: [PATCH 01/20] feat: reimplement designs stub + refactor --- .../geometry/core/_grpc/_services/_service.py | 176 ++++++++++-------- .../core/_grpc/_services/base/designs.py | 85 +++++++++ .../core/_grpc/_services/v0/conversions.py | 40 ++++ .../core/_grpc/_services/v0/designs.py | 176 ++++++++++++++++++ .../_grpc/_services/v1/coordinate_systems.py | 2 +- .../core/_grpc/_services/v1/designs.py | 86 +++++++++ .../_grpc/_services/v1/driving_dimensions.py | 2 +- .../_grpc/_services/v1/measurement_tools.py | 2 +- .../_grpc/_services/v1/named_selection.py | 2 +- .../core/_grpc/_services/v1/prepare_tools.py | 2 +- .../core/_grpc/_services/v1/repair_tools.py | 32 +++- src/ansys/geometry/core/designer/body.py | 40 ++-- src/ansys/geometry/core/designer/design.py | 89 ++++----- src/ansys/geometry/core/modeler.py | 7 +- 14 files changed, 582 insertions(+), 159 deletions(-) create mode 100644 src/ansys/geometry/core/_grpc/_services/base/designs.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v0/designs.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v1/designs.py diff --git a/src/ansys/geometry/core/_grpc/_services/_service.py b/src/ansys/geometry/core/_grpc/_services/_service.py index f72432144e..b0a48e5a07 100644 --- a/src/ansys/geometry/core/_grpc/_services/_service.py +++ b/src/ansys/geometry/core/_grpc/_services/_service.py @@ -26,6 +26,7 @@ from .base.admin import GRPCAdminService from .base.bodies import GRPCBodyService from .base.coordinate_systems import GRPCCoordinateSystemService +from .base.designs import GRPCDesignsService from .base.dbuapplication import GRPCDbuApplicationService from .base.driving_dimensions import GRPCDrivingDimensionsService from .base.measurement_tools import GRPCMeasurementToolsService @@ -74,13 +75,40 @@ def __init__(self, channel: grpc.Channel, version: GeometryApiProtos | str | Non # Lazy load all the services self._admin = None self._bodies = None + self._coordinate_systems = None self._dbu_application = None - self._named_selection = None + self._designs = None + self._driving_dimensions = None self._measurement_tools = None - self._repair_tools = None + self._named_selection = None self._prepare_tools = None - self._driving_dimensions = None - self._coordinate_systems = None + self._repair_tools = None + + @property + def admin(self) -> GRPCAdminService: + """ + Get the admin service for the specified version. + + Returns + ------- + GRPCAdminService + The admin service for the specified version. + """ + if not self._admin: + # Import the appropriate admin service based on the version + from .v0.admin import GRPCAdminServiceV0 + from .v1.admin import GRPCAdminServiceV1 + + if self.version == GeometryApiProtos.V0: + self._admin = GRPCAdminServiceV0(self.channel) + elif self.version == GeometryApiProtos.V1: # pragma: no cover + # V1 is not implemented yet + self._admin = GRPCAdminServiceV1(self.channel) + else: # pragma: no cover + # This should never happen as the version is set in the constructor + raise ValueError(f"Unsupported version: {self.version}") + + return self._admin @property def bodies(self) -> GRPCBodyService: @@ -109,30 +137,56 @@ def bodies(self) -> GRPCBodyService: return self._bodies @property - def admin(self) -> GRPCAdminService: + def coordinate_systems(self) -> GRPCCoordinateSystemService: """ - Get the admin service for the specified version. + Get the coordinate systems service for the specified version. Returns ------- - GRPCAdminService - The admin service for the specified version. + GRPCCoordinateSystemService + The coordinate systems service for the specified version. """ - if not self._admin: - # Import the appropriate admin service based on the version - from .v0.admin import GRPCAdminServiceV0 - from .v1.admin import GRPCAdminServiceV1 + if not self._coordinate_systems: + # Import the appropriate coordinate systems service based on the version + from .v0.coordinate_systems import GRPCCoordinateSystemServiceV0 + from .v1.coordinate_systems import GRPCCoordinateSystemServiceV1 if self.version == GeometryApiProtos.V0: - self._admin = GRPCAdminServiceV0(self.channel) + self._coordinate_systems = GRPCCoordinateSystemServiceV0(self.channel) elif self.version == GeometryApiProtos.V1: # pragma: no cover # V1 is not implemented yet - self._admin = GRPCAdminServiceV1(self.channel) + self._coordinate_systems = GRPCCoordinateSystemServiceV1(self.channel) else: # pragma: no cover # This should never happen as the version is set in the constructor raise ValueError(f"Unsupported version: {self.version}") - return self._admin + return self._coordinate_systems + + @property + def designs(self) -> GRPCDesignsService: + """ + Get the designs service for the specified version. + + Returns + ------- + GRPCDesignsService + The designs service for the specified version. + """ + if not self._designs: + # Import the appropriate designs service based on the version + from .v0.designs import GRPCDesignsServiceV0 + from .v1.designs import GRPCDesignsServiceV1 + + if self.version == GeometryApiProtos.V0: + self._designs = GRPCDesignsServiceV0(self.channel) + elif self.version == GeometryApiProtos.V1: # pragma: no cover + # V1 is not implemented yet + self._designs = GRPCDesignsServiceV1(self.channel) + else: # pragma: no cover + # This should never happen as the version is set in the constructor + raise ValueError(f"Unsupported version: {self.version}") + + return self._designs @property def dbu_application(self) -> GRPCDbuApplicationService: @@ -161,30 +215,30 @@ def dbu_application(self) -> GRPCDbuApplicationService: return self._dbu_application @property - def named_selection(self) -> GRPCNamedSelectionService: + def driving_dimensions(self) -> GRPCDrivingDimensionsService: """ - Get the named selection service for the specified version. + Get the driving dimensions service for the specified version. Returns ------- - GRPCNamedSelectionService - The named selection service for the specified version. + GRPCDrivingDimensionsService + The driving dimensions service for the specified version. """ - if not self._named_selection: - # Import the appropriate named selection service based on the version - from .v0.named_selection import GRPCNamedSelectionServiceV0 - from .v1.named_selection import GRPCNamedSelectionServiceV1 + if not self._driving_dimensions: + # Import the appropriate driving dimensions service based on the version + from .v0.driving_dimensions import GRPCDrivingDimensionsServiceV0 + from .v1.driving_dimensions import GRPCDrivingDimensionsServiceV1 if self.version == GeometryApiProtos.V0: - self._named_selection = GRPCNamedSelectionServiceV0(self.channel) + self._driving_dimensions = GRPCDrivingDimensionsServiceV0(self.channel) elif self.version == GeometryApiProtos.V1: # pragma: no cover # V1 is not implemented yet - self._named_selection = GRPCNamedSelectionServiceV1(self.channel) + self._driving_dimensions = GRPCDrivingDimensionsServiceV1(self.channel) else: # pragma: no cover # This should never happen as the version is set in the constructor raise ValueError(f"Unsupported version: {self.version}") - return self._named_selection + return self._driving_dimensions @property def measurement_tools(self) -> GRPCMeasurementToolsService: @@ -213,28 +267,30 @@ def measurement_tools(self) -> GRPCMeasurementToolsService: return self._measurement_tools @property - def repair_tools(self) -> GRPCRepairToolsService: + def named_selection(self) -> GRPCNamedSelectionService: """ - Get the repair tools service for the specified version. + Get the named selection service for the specified version. Returns ------- - RepairToolsServiceBase - The repair tools service for the specified version. + GRPCNamedSelectionService + The named selection service for the specified version. """ - if not self._repair_tools: - from .v0.repair_tools import GRPCRepairToolsServiceV0 - from .v1.repair_tools import GRPCRepairToolsServiceV1 + if not self._named_selection: + # Import the appropriate named selection service based on the version + from .v0.named_selection import GRPCNamedSelectionServiceV0 + from .v1.named_selection import GRPCNamedSelectionServiceV1 if self.version == GeometryApiProtos.V0: - self._repair_tools = GRPCRepairToolsServiceV0(self.channel) + self._named_selection = GRPCNamedSelectionServiceV0(self.channel) elif self.version == GeometryApiProtos.V1: # pragma: no cover # V1 is not implemented yet - self._repair_tools = GRPCRepairToolsServiceV1(self.channel) + self._named_selection = GRPCNamedSelectionServiceV1(self.channel) else: # pragma: no cover # This should never happen as the version is set in the constructor raise ValueError(f"Unsupported version: {self.version}") - return self._repair_tools + + return self._named_selection @property def prepare_tools(self) -> GRPCPrepareToolsService: @@ -263,53 +319,25 @@ def prepare_tools(self) -> GRPCPrepareToolsService: return self._prepare_tools @property - def driving_dimensions(self) -> GRPCDrivingDimensionsService: - """ - Get the driving dimensions service for the specified version. - - Returns - ------- - GRPCDrivingDimensionsService - The driving dimensions service for the specified version. - """ - if not self._driving_dimensions: - # Import the appropriate driving dimensions service based on the version - from .v0.driving_dimensions import GRPCDrivingDimensionsServiceV0 - from .v1.driving_dimensions import GRPCDrivingDimensionsServiceV1 - - if self.version == GeometryApiProtos.V0: - self._driving_dimensions = GRPCDrivingDimensionsServiceV0(self.channel) - elif self.version == GeometryApiProtos.V1: # pragma: no cover - # V1 is not implemented yet - self._driving_dimensions = GRPCDrivingDimensionsServiceV1(self.channel) - else: # pragma: no cover - # This should never happen as the version is set in the constructor - raise ValueError(f"Unsupported version: {self.version}") - - return self._driving_dimensions - - @property - def coordinate_systems(self) -> GRPCCoordinateSystemService: + def repair_tools(self) -> GRPCRepairToolsService: """ - Get the coordinate systems service for the specified version. + Get the repair tools service for the specified version. Returns ------- - GRPCCoordinateSystemService - The coordinate systems service for the specified version. + RepairToolsServiceBase + The repair tools service for the specified version. """ - if not self._coordinate_systems: - # Import the appropriate coordinate systems service based on the version - from .v0.coordinate_systems import GRPCCoordinateSystemServiceV0 - from .v1.coordinate_systems import GRPCCoordinateSystemServiceV1 + if not self._repair_tools: + from .v0.repair_tools import GRPCRepairToolsServiceV0 + from .v1.repair_tools import GRPCRepairToolsServiceV1 if self.version == GeometryApiProtos.V0: - self._coordinate_systems = GRPCCoordinateSystemServiceV0(self.channel) + self._repair_tools = GRPCRepairToolsServiceV0(self.channel) elif self.version == GeometryApiProtos.V1: # pragma: no cover # V1 is not implemented yet - self._coordinate_systems = GRPCCoordinateSystemServiceV1(self.channel) + self._repair_tools = GRPCRepairToolsServiceV1(self.channel) else: # pragma: no cover # This should never happen as the version is set in the constructor raise ValueError(f"Unsupported version: {self.version}") - - return self._coordinate_systems + return self._repair_tools diff --git a/src/ansys/geometry/core/_grpc/_services/base/designs.py b/src/ansys/geometry/core/_grpc/_services/base/designs.py new file mode 100644 index 0000000000..ad2ea40bb8 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/base/designs.py @@ -0,0 +1,85 @@ +# 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 containing the designs service implementation (abstraction layer).""" + +from abc import ABC, abstractmethod + +import grpc + + +class GRPCDesignsService(ABC): + """Designs service for gRPC communication with the Geometry server. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + def __init__(self, channel: grpc.Channel): + """Initialize the GRPCDesignsService class.""" + pass # pragma: no cover + + @abstractmethod + def open(self, **kwargs) -> dict: + """Open a design on the service.""" + pass # pragma: no cover + + @abstractmethod + def new(self, **kwargs) -> dict: + """Create a new design.""" + pass # pragma: no cover + + @abstractmethod + def close(self, **kwargs) -> dict: + """Close the currently open design.""" + pass # pragma: no cover + + @abstractmethod + def put_active(self, **kwargs) -> dict: + """Activate an already opened design on the service.""" + pass # pragma: no cover + + @abstractmethod + def save_as(self, **kwargs) -> dict: + """Create a new design.""" + pass # pragma: no cover + + @abstractmethod + def download_export(self, **kwargs) -> dict: + """Download and export a design into a certain format.""" + pass # pragma: no cover + + @abstractmethod + def stream_download_export(self, **kwargs) -> dict: + """Download and export a design into a certain format.""" + pass # pragma: no cover + + @abstractmethod + def insert(self, **kwargs) -> dict: + """Insert a part/component/design into an existing design.""" + pass # pragma: no cover + + @abstractmethod + def get_active(self, **kwargs) -> dict: + """Get the active design on the service.""" + pass # pragma: no cover \ No newline at end of file diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index cb1a1ea6bc..52ebb32d5a 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -29,6 +29,7 @@ from ansys.api.dbu.v0.dbumodels_pb2 import ( DrivingDimension as GRPCDrivingDimension, EntityIdentifier, + PartExportFormat as GRPCPartExportFormat, ) from ansys.api.dbu.v0.drivingdimensions_pb2 import UpdateStatus as GRPCUpdateStatus from ansys.api.geometry.v0.models_pb2 import ( @@ -58,6 +59,7 @@ import pyvista as pv from ansys.geometry.core.connection.backend import BackendType + from ansys.geometry.core.designer.design import DesignFileFormat from ansys.geometry.core.materials.material import Material from ansys.geometry.core.materials.property import MaterialProperty from ansys.geometry.core.math.frame import Frame @@ -848,3 +850,41 @@ def from_grpc_update_status_to_parameter_update_status( GRPCUpdateStatus.CONSTRAINED_PARAMETERS: ParameterUpdateStatus.CONSTRAINED_PARAMETERS, } return status_mapping.get(update_status, ParameterUpdateStatus.UNKNOWN) + +def from_design_file_format_to_grpc_part_export_format( + design_file_format: "DesignFileFormat", +) -> GRPCPartExportFormat: + """Convert from a DesignFileFormat object to a gRPC PartExportFormat one. + + Parameters + ---------- + design_file_format : DesignFileFormat + The file format desired + + Returns + ------- + GRPCPartExportFormat + Converted gRPC Part format + """ + from ansys.geometry.core.designer.design import DesignFileFormat + + if design_file_format == DesignFileFormat.SCDOCX: + return GRPCPartExportFormat.PARTEXPORTFORMAT_SCDOCX + elif design_file_format == DesignFileFormat.PARASOLID_TEXT: + return GRPCPartExportFormat.PARTEXPORTFORMAT_PARASOLID_TEXT + elif design_file_format == DesignFileFormat.PARASOLID_BIN: + return GRPCPartExportFormat.PARTEXPORTFORMAT_PARASOLID_BINARY + elif design_file_format == DesignFileFormat.FMD: + return GRPCPartExportFormat.PARTEXPORTFORMAT_FMD + elif design_file_format == DesignFileFormat.STEP: + return GRPCPartExportFormat.PARTEXPORTFORMAT_STEP + elif design_file_format == DesignFileFormat.IGES: + return GRPCPartExportFormat.PARTEXPORTFORMAT_IGES + elif design_file_format == DesignFileFormat.PMDB: + return GRPCPartExportFormat.PARTEXPORTFORMAT_PMDB + elif design_file_format == DesignFileFormat.STRIDE: + return GRPCPartExportFormat.PARTEXPORTFORMAT_STRIDE + elif design_file_format == DesignFileFormat.DISCO: + return GRPCPartExportFormat.PARTEXPORTFORMAT_DISCO + else: + return None \ No newline at end of file diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py new file mode 100644 index 0000000000..0183be8e5b --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -0,0 +1,176 @@ +# 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 containing the designs service implementation for v0.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.designs import GRPCDesignsService +from .conversions import build_grpc_id, from_design_file_format_to_grpc_part_export_format + + +class GRPCDesignsServiceV0(GRPCDesignsService): # pragma: no cover + """Designs service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + designs service. It is specifically designed for the v0 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.dbu.v0.designs_pb2_grpc import DesignsStub + + self.stub = DesignsStub(channel) + + @protect_grpc + def open(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.dbu.v0.designs_pb2 import OpenRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = OpenRequest( + fliepath=kwargs["filepath"], + import_options=kwargs["import_options"].to_dict(), + ) + + # Call the gRPC service + _ = self.stub.Open(request) + + # Return the response - formatted as a dictionary + return {} + + @protect_grpc + def new(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.dbu.v0.designs_pb2 import NewRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = NewRequest(name=kwargs["name"]) + + # Call the gRPC service + response = self.stub.New(request) + + # Return the response - formatted as a dictionary + return { + "design_id": response.id, + "main_part_id": response.main_part.id, + } + + @protect_grpc + def close(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(id=kwargs["design_id"]) + + # Call the gRPC service + _ = self.stub.Close(request) + + # Return the response - formatted as a dictionary + return {} + + @protect_grpc + def put_active(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(id=kwargs["design_id"]) + + # Call the gRPC service + _ = self.stub.PutActive(request) + + # Return the response - formatted as a dictionary + return {} + + @protect_grpc + def save_as(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.dbu.v0.designs_pb2 import SaveAsRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = SaveAsRequest(filepath=kwargs["filepath"]) + + # Call the gRPC service + _ = self.stub.SaveAs(request) + + # Return the response - formatted as a dictionary + return {} + + @protect_grpc + def download_export(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.dbu.v0.designs_pb2 import DownloadExportFileRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = DownloadExportFileRequest(format=from_design_file_format_to_grpc_part_export_format(kwargs["format"])) + + # Call the gRPC service + response = self.stub.DownloadExportFile(request) + + # Return the response - formatted as a dictionary + data = bytes() + data += response.data + return {"data" : data} + + @protect_grpc + def stream_download_export(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.dbu.v0.designs_pb2 import DownloadExportFileRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = DownloadExportFileRequest(format=from_design_file_format_to_grpc_part_export_format(kwargs["format"])) + + # Call the gRPC service + response = self.stub.StreamDownloadExportFile(request) + + # Return the response - formatted as a dictionary + data = bytes() + for elem in response: + data += response.data + + return {"data" : data} + + @protect_grpc + def insert(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.dbu.v0.designs_pb2 import InsertRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = InsertRequest(filepath=kwargs["filepath"]) + + # Call the gRPC service + _ = self.stub.Insert(request) + + # Return the response - formatted as a dictionary + return {} + + @protect_grpc + def get_active(self, **kwargs) -> dict: # noqa: D102 + from google.protobuf.empty_pb2 import Empty + + # Call the gRPC service + response = self.stub.GetActive(request=Empty()) + + # Return the response - formatted as a dictionary + if response: + return { + "design_id": response.id, + "main_part_id": response.main_part.id, + "name": response.name, + } diff --git a/src/ansys/geometry/core/_grpc/_services/v1/coordinate_systems.py b/src/ansys/geometry/core/_grpc/_services/v1/coordinate_systems.py index f54a95c036..193de7c26e 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/coordinate_systems.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/coordinate_systems.py @@ -28,7 +28,7 @@ from ..base.coordinate_systems import GRPCCoordinateSystemService -class GRPCCoordinateSystemServiceV1(GRPCCoordinateSystemService): +class GRPCCoordinateSystemServiceV1(GRPCCoordinateSystemService): # pragma: no cover """Coordinate systems service for gRPC communication with the Geometry server. This class provides methods to interact with the Geometry server's diff --git a/src/ansys/geometry/core/_grpc/_services/v1/designs.py b/src/ansys/geometry/core/_grpc/_services/v1/designs.py new file mode 100644 index 0000000000..eecb4b173b --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v1/designs.py @@ -0,0 +1,86 @@ +# 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 containing the designs service implementation for v1.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.designs import GRPCDesignsService + + +class GRPCDesignsServiceV1(GRPCDesignsService): # pragma: no cover + """Designs service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + designs service. It is specifically designed for the v1 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.dbu.v1.designs_pb2_grpc import DesignsStub + + self.stub = DesignsStub(channel) + + @protect_grpc + def open(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def new(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def close(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def put_active(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def save_as(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def download_export(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def stream_download_export(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def insert(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_active(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + diff --git a/src/ansys/geometry/core/_grpc/_services/v1/driving_dimensions.py b/src/ansys/geometry/core/_grpc/_services/v1/driving_dimensions.py index 4d53c4fcdb..6164905944 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/driving_dimensions.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/driving_dimensions.py @@ -28,7 +28,7 @@ from ..base.driving_dimensions import GRPCDrivingDimensionsService -class GRPCDrivingDimensionsServiceV1(GRPCDrivingDimensionsService): +class GRPCDrivingDimensionsServiceV1(GRPCDrivingDimensionsService): # pragma: no cover """Driving Dimensions service for gRPC communication with the Geometry server. This class provides methods to interact with the Geometry server's diff --git a/src/ansys/geometry/core/_grpc/_services/v1/measurement_tools.py b/src/ansys/geometry/core/_grpc/_services/v1/measurement_tools.py index a1ac9ccf61..52ae160e58 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/measurement_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/measurement_tools.py @@ -28,7 +28,7 @@ from ..base.measurement_tools import GRPCMeasurementToolsService -class GRPCMeasurementToolsServiceV1(GRPCMeasurementToolsService): +class GRPCMeasurementToolsServiceV1(GRPCMeasurementToolsService): # pragma: no cover """Measurement tools service for gRPC communication with the Geometry server. This class provides methods to interact with the Geometry server's diff --git a/src/ansys/geometry/core/_grpc/_services/v1/named_selection.py b/src/ansys/geometry/core/_grpc/_services/v1/named_selection.py index ba2b63d88e..57fd83f9d8 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/named_selection.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/named_selection.py @@ -28,7 +28,7 @@ from ..base.named_selection import GRPCNamedSelectionService -class GRPCNamedSelectionServiceV1(GRPCNamedSelectionService): +class GRPCNamedSelectionServiceV1(GRPCNamedSelectionService): # pragma: no cover """Named Selection service for gRPC communication with the Geometry server. This class provides methods to interact with the Geometry server's diff --git a/src/ansys/geometry/core/_grpc/_services/v1/prepare_tools.py b/src/ansys/geometry/core/_grpc/_services/v1/prepare_tools.py index 1e6f162c6e..dc3beddf42 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/prepare_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/prepare_tools.py @@ -28,7 +28,7 @@ from ..base.prepare_tools import GRPCPrepareToolsService -class GRPCPrepareToolsServiceV1(GRPCPrepareToolsService): +class GRPCPrepareToolsServiceV1(GRPCPrepareToolsService): # pragma: no cover """Prepare tools service for gRPC communication with the Geometry server. This class provides methods to interact with the Geometry server's diff --git a/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py b/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py index 75c23aa7f1..0c9455ffc1 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py @@ -21,12 +21,13 @@ # SOFTWARE. """Module containing the repair tools service implementation.""" -from abc import ABC - import grpc +from ansys.geometry.core.errors import protect_grpc + +from ..base.repair_tools import GRPCRepairToolsService -class GRPCRepairToolsServiceV1(ABC): +class GRPCRepairToolsServiceV1(GRPCRepairToolsService): # pragma: no cover """Repair tools service for gRPC communication with the Geometry server. Parameters @@ -35,53 +36,72 @@ class GRPCRepairToolsServiceV1(ABC): The gRPC channel to the server. """ - def __init__(self, channel: grpc.Channel): - """Initialize the MeasurementToolsService class.""" - + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v1.repairtools_pb2_grpc import RepairToolsStub + + self.stub = RepairToolsStub(channel) + + @protect_grpc def find_split_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_extra_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_inexact_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_short_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_duplicate_faces(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_missing_faces(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_small_faces(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_stitch_faces(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_simplify(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_interferences(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_and_fix_short_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_and_fix_extra_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_and_fix_split_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def find_and_fix_simplify(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def inspect_geometry(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc def repair_geometry(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index e5c37e521a..0550c95653 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -914,7 +914,7 @@ def faces(self) -> list[Face]: # noqa: D102 def _get_faces_from_id(self, body: Union["Body", "MasterBody"]) -> list[Face]: """Retrieve faces from a body ID.""" self._grpc_client.log.debug(f"Retrieving faces for body {body.id} from server.") - resp = self._grpc_client.services.bodies.get_faces(id=body.id) + response = self._grpc_client.services.bodies.get_faces(id=body.id) return [ Face( face_resp.get("id"), @@ -923,7 +923,7 @@ def _get_faces_from_id(self, body: Union["Body", "MasterBody"]) -> list[Face]: self._grpc_client, face_resp.get("is_reversed"), ) - for face_resp in resp.get("faces") + for face_resp in response.get("faces") ] @property @@ -933,7 +933,7 @@ def edges(self) -> list[Edge]: # noqa: D102 def _get_edges_from_id(self, body: Union["Body", "MasterBody"]) -> list[Edge]: """Retrieve edges from a body ID.""" self._grpc_client.log.debug(f"Retrieving edges for body {body.id} from server.") - resp = self._grpc_client.services.bodies.get_edges(id=body.id) + response = self._grpc_client.services.bodies.get_edges(id=body.id) return [ Edge( edge_resp.get("id"), @@ -942,7 +942,7 @@ def _get_edges_from_id(self, body: Union["Body", "MasterBody"]) -> list[Edge]: self._grpc_client, edge_resp.get("is_reversed"), ) - for edge_resp in resp.get("edges") + for edge_resp in response.get("edges") ] @property @@ -956,8 +956,8 @@ def volume(self) -> Quantity: # noqa: D102 return Quantity(0, DEFAULT_UNITS.SERVER_VOLUME) else: self._grpc_client.log.debug(f"Retrieving volume for body {self.id} from server.") - resp = self._grpc_client.services.bodies.get_volume(id=self.id) - return resp.get("volume") + response = self._grpc_client.services.bodies.get_volume(id=self.id) + return response.get("volume") @property def material(self) -> Material: # noqa: D102 @@ -971,11 +971,11 @@ def material(self, value: Material): # noqa: D102 @min_backend_version(25, 2, 0) def bounding_box(self) -> BoundingBox: # noqa: D102 self._grpc_client.log.debug(f"Retrieving bounding box for body {self.id} from server.") - resp = self._grpc_client.services.bodies.get_bounding_box(id=self.id) + response = self._grpc_client.services.bodies.get_bounding_box(id=self.id) return BoundingBox( - min_corner=resp.get("min"), - max_corner=resp.get("max"), - center=resp.get("center"), + min_corner=response.get("min"), + max_corner=response.get("max"), + center=response.get("center"), ) @check_input_types @@ -985,8 +985,8 @@ def assign_material(self, material: Material) -> None: # noqa: D102 def get_assigned_material(self) -> Material: # noqa: D102 self._grpc_client.log.debug(f"Retrieving assigned material for body {self.id}.") - resp = self._grpc_client.services.bodies.get_assigned_material(id=self.id) - return resp.get("material") + response = self._grpc_client.services.bodies.get_assigned_material(id=self.id) + return response.get("material") @protect_grpc @check_input_types @@ -1157,8 +1157,8 @@ def mirror(self, plane: Plane) -> None: # noqa: D102 @min_backend_version(24, 2, 0) def get_collision(self, body: "Body") -> CollisionType: # noqa: D102 self._grpc_client.log.debug(f"Get collision between body {self.id} and body {body.id}.") - resp = self._grpc_client.services.bodies.get_collision(id=self.id, other_id=body.id) - return CollisionType(resp.get("collision_type")) + response = self._grpc_client.services.bodies.get_collision(id=self.id, other_id=body.id) + return CollisionType(response.get("collision_type")) def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 from ansys.geometry.core.designer.component import Component @@ -1169,18 +1169,18 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 check_type(copy_name, str) self._grpc_client.log.debug(f"Copying body {self.id}.") - resp = self._grpc_client.services.bodies.copy( + response = self._grpc_client.services.bodies.copy( id=self.id, parent_id=parent.id, name=copy_name ) # Assign the new body to its specified parent (and return the new body) tb = MasterBody( - resp.get("master_id"), copy_name, self._grpc_client, is_surface=self.is_surface + response.get("master_id"), copy_name, self._grpc_client, is_surface=self.is_surface ) parent._master_component.part.bodies.append(tb) parent._clear_cached_bodies() body_id = f"{parent.id}/{tb.id}" if parent.parent_component else tb.id - return Body(body_id, resp.get("name"), parent, tb) + return Body(body_id, response.get("name"), parent, tb) @graphics_required def tessellate( # noqa: D102 @@ -1208,17 +1208,17 @@ def tessellate( # noqa: D102 # cache tessellation if not self._tessellation: if tess_options is not None: - resp = self._grpc_client.services.bodies.get_tesellation_with_options( + response = self._grpc_client.services.bodies.get_tesellation_with_options( id=self.id, options=tess_options, ) else: - resp = self._grpc_client.services.bodies.get_tesellation( + response = self._grpc_client.services.bodies.get_tesellation( id=self.id, backend_version=self._grpc_client.backend_version, ) - self._tessellation = resp.get("tessellation") + self._tessellation = response.get("tessellation") pdata = [tess.transform(transform, inplace=False) for tess in self._tessellation.values()] comp = pv.MultiBlock(pdata) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 17f1a6f409..d2b84d3bc7 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -99,17 +99,20 @@ class DesignFileFormat(Enum): """Provides supported file formats that can be downloaded for designs.""" - SCDOCX = "SCDOCX", PartExportFormat.PARTEXPORTFORMAT_SCDOCX - PARASOLID_TEXT = "PARASOLID_TEXT", PartExportFormat.PARTEXPORTFORMAT_PARASOLID_TEXT - PARASOLID_BIN = "PARASOLID_BIN", PartExportFormat.PARTEXPORTFORMAT_PARASOLID_BINARY - FMD = "FMD", PartExportFormat.PARTEXPORTFORMAT_FMD - STEP = "STEP", PartExportFormat.PARTEXPORTFORMAT_STEP - IGES = "IGES", PartExportFormat.PARTEXPORTFORMAT_IGES - PMDB = "PMDB", PartExportFormat.PARTEXPORTFORMAT_PMDB - STRIDE = "STRIDE", PartExportFormat.PARTEXPORTFORMAT_STRIDE - DISCO = "DISCO", PartExportFormat.PARTEXPORTFORMAT_DISCO - INVALID = "INVALID", None - + SCDOCX = "SCDOCX" + PARASOLID_TEXT = "PARASOLID_TEXT" + PARASOLID_BIN = "PARASOLID_BIN" + FMD = "FMD" + STEP = "STEP" + IGES = "IGES" + PMDB = "PMDB" + STRIDE = "STRIDE" + DISCO = "DISCO" + INVALID = "INVALID" + + def __str__(self): + """Represent object in string format""" + return self.value class Design(Component): """Provides for organizing geometry assemblies. @@ -140,7 +143,6 @@ def __init__(self, name: str, modeler: Modeler, read_existing_design: bool = Fal super().__init__(name, None, modeler.client) # Initialize the stubs needed - self._design_stub = DesignsStub(self._grpc_client.channel) self._commands_stub = CommandsStub(self._grpc_client.channel) self._materials_stub = MaterialsStub(self._grpc_client.channel) self._parts_stub = PartsStub(self._grpc_client.channel) @@ -158,9 +160,9 @@ def __init__(self, name: str, modeler: Modeler, read_existing_design: bool = Fal self._grpc_client.log.debug("Reading Design object from service.") self.__read_existing_design() else: - new_design = self._design_stub.New(NewRequest(name=name)) - self._design_id = new_design.id - self._id = new_design.main_part.id + response = self._grpc_client.services.designs.new(name=name) + self._design_id = response.get("design_id") + self._id = response.get("main_part_id") self._activate(called_after_design_creation=True) self._grpc_client.log.debug("Design object instantiated successfully.") @@ -208,7 +210,7 @@ def close(self) -> None: # Attempt to close the design try: - self._design_stub.Close(EntityIdentifier(id=self._design_id)) + self._grpc_client.services.designs.close(design_id=self._design_id) except Exception as err: self._grpc_client.log.warning(f"Design {self.name} could not be closed. Error: {err}.") self._grpc_client.log.warning("Ignoring response and assuming the design is closed.") @@ -216,12 +218,11 @@ def close(self) -> None: # Consider the design closed (even if the close request failed) self._is_active = False - @protect_grpc def _activate(self, called_after_design_creation: bool = False) -> None: """Activate the design.""" # Activate the current design if not called_after_design_creation: - self._design_stub.PutActive(EntityIdentifier(id=self._design_id)) + self._grpc_client.services.designs.put_active(design_id=self._design_id) self._is_active = True self._grpc_client.log.debug(f"Design {self.name} is activated.") @@ -260,7 +261,6 @@ def add_material(self, material: Material) -> None: self._grpc_client.log.debug(f"Material {material.name} is successfully added to design.") - @protect_grpc @check_input_types @ensure_design_is_active def save(self, file_location: Path | str) -> None: @@ -275,7 +275,7 @@ def save(self, file_location: Path | str) -> None: if isinstance(file_location, Path): file_location = str(file_location) - self._design_stub.SaveAs(SaveAsRequest(filepath=file_location)) + self._grpc_client.services.designs.save_as(filepath=file_location) self._grpc_client.log.debug(f"Design successfully saved at location {file_location}.") @protect_grpc @@ -305,7 +305,7 @@ def download( file_location.parent.mkdir(parents=True, exist_ok=True) # Process response - self._grpc_client.log.debug(f"Requesting design download in {format.value[0]} format.") + self._grpc_client.log.debug(f"Requesting design download in {format} format.") if self._modeler.client.backend_version < (25, 2, 0): received_bytes = self.__export_and_download_legacy(format=format) else: @@ -334,7 +334,7 @@ def __export_and_download_legacy(self, format: DesignFileFormat) -> bytes: up to Ansys 25.1.1 products. """ # Process response - self._grpc_client.log.debug(f"Requesting design download in {format.value[0]} format.") + self._grpc_client.log.debug(f"Requesting design download in {format} format.") received_bytes = bytes() if format is DesignFileFormat.SCDOCX: response = self._commands_stub.DownloadFile(Empty()) @@ -347,11 +347,11 @@ def __export_and_download_legacy(self, format: DesignFileFormat) -> bytes: DesignFileFormat.IGES, DesignFileFormat.PMDB, ]: - response = self._parts_stub.Export(ExportRequest(format=format.value[1])) + response = self._parts_stub.Export(ExportRequest(format=format[1])) received_bytes += response.data else: self._grpc_client.log.warning( - f"{format.value[0]} format requested is not supported. Ignoring download request." + f"{format} format requested is not supported. Ignoring download request." ) return @@ -371,8 +371,7 @@ def __export_and_download(self, format: DesignFileFormat) -> bytes: The raw data from the exported and downloaded file. """ # Process response - self._grpc_client.log.debug(f"Requesting design download in {format.value[0]} format.") - received_bytes = bytes() + self._grpc_client.log.debug(f"Requesting design download in {format} format.") if format in [ DesignFileFormat.PARASOLID_TEXT, @@ -386,29 +385,21 @@ def __export_and_download(self, format: DesignFileFormat) -> bytes: DesignFileFormat.STRIDE, ]: try: - response = self._design_stub.DownloadExportFile( - DownloadExportFileRequest(format=format.value[1]) - ) - received_bytes += response.data + response = self._grpc_client.services.designs.download_export(format=format) except Exception: self._grpc_client.log.warning( - f"Failed to download the file in {format.value[0]} format." + f"Failed to download the file in {format} format." " Attempting to stream download." ) # Attempt to download the file via streaming - received_bytes = bytes() - responses = self._design_stub.StreamDownloadExportFile( - DownloadExportFileRequest(format=format.value[1]) - ) - for response in responses: - received_bytes += response.data + response = self._grpc_client.services.designs.stream_download_export(format=format) else: self._grpc_client.log.warning( - f"{format.value[0]} format requested is not supported. Ignoring download request." + f"{format} format requested is not supported. Ignoring download request." ) - return + return None - return received_bytes + return response.get("data") def __build_export_file_location(self, location: Path | str | None, ext: str) -> Path: """Build the file location for export functions. @@ -984,7 +975,7 @@ def insert_file( filepath_server = self._modeler._upload_file(file_location, import_options=import_options) # Insert the file into the design - self._design_stub.Insert(InsertRequest(filepath=filepath_server)) + self._grpc_client.services.designs.insert(filepath=filepath_server) self._grpc_client.log.debug(f"File {file_location} successfully inserted into design.") self._update_design_inplace() @@ -1045,28 +1036,26 @@ def __read_existing_design(self) -> None: start = time.time() # Grab active design - design = self._design_stub.GetActive(Empty()) - if not design: + design_response = self._grpc_client.services.designs.get_active() + if not design_response: raise RuntimeError("No existing design available at service level.") else: - self._design_id = design.id - self._id = design.main_part.id + self._design_id = design_response.get("design_id") + self._id = design_response.get("main_part_id") + self._name = design_response.get("name") self._activate(called_after_design_creation=True) - # Here we may take the design's name instead of the main part's name. - # Since they're the same in the backend. - self._name = design.name response = self._commands_stub.GetAssembly(EntityIdentifier(id=self._design_id)) # Store created objects created_parts = {p.id: Part(p.id, p.name, [], []) for p in response.parts} created_tps = {} - created_components = {design.main_part.id: self} + created_components = {design_response.get("main_part_id"): self} created_bodies = {} # Make dummy master for design since server doesn't have one self._master_component = MasterComponent( - "1", "master_design", created_parts[design.main_part.id] + "1", "master_design", created_parts[design_response.get("main_part_id")] ) # Create MasterComponents diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index a43a85626e..56a4fb991e 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -27,8 +27,6 @@ from grpc import Channel -from ansys.api.dbu.v0.designs_pb2 import OpenRequest -from ansys.api.dbu.v0.designs_pb2_grpc import DesignsStub from ansys.api.geometry.v0.commands_pb2 import UploadFileRequest from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub from ansys.geometry.core.connection.backend import ApiVersions, BackendType @@ -451,8 +449,9 @@ def open_file( "File is too large to upload. Service versions above 25R2 support streaming." ) else: - DesignsStub(self.client.channel).Open( - OpenRequest(filepath=file_path, import_options=import_options.to_dict()) + self.client.services.designs.open( + filepath=file_path, + import_options=import_options, ) return self.read_existing_design() From 07b6e2743024005b08792b1dadd6e28e38bec0b6 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 15 May 2025 02:54:20 +0200 Subject: [PATCH 02/20] fix: pre-commit --- .../geometry/core/_grpc/_services/_service.py | 2 +- .../core/_grpc/_services/base/designs.py | 10 ++++---- .../core/_grpc/_services/v0/conversions.py | 3 ++- .../core/_grpc/_services/v0/designs.py | 24 +++++++++++-------- .../core/_grpc/_services/v1/designs.py | 14 +++++------ .../core/_grpc/_services/v1/repair_tools.py | 5 ++-- src/ansys/geometry/core/designer/design.py | 12 +++------- 7 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/_service.py b/src/ansys/geometry/core/_grpc/_services/_service.py index b0a48e5a07..3fbeedbe27 100644 --- a/src/ansys/geometry/core/_grpc/_services/_service.py +++ b/src/ansys/geometry/core/_grpc/_services/_service.py @@ -26,8 +26,8 @@ from .base.admin import GRPCAdminService from .base.bodies import GRPCBodyService from .base.coordinate_systems import GRPCCoordinateSystemService -from .base.designs import GRPCDesignsService from .base.dbuapplication import GRPCDbuApplicationService +from .base.designs import GRPCDesignsService from .base.driving_dimensions import GRPCDrivingDimensionsService from .base.measurement_tools import GRPCMeasurementToolsService from .base.named_selection import GRPCNamedSelectionService diff --git a/src/ansys/geometry/core/_grpc/_services/base/designs.py b/src/ansys/geometry/core/_grpc/_services/base/designs.py index ad2ea40bb8..dbc3ffd2a6 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/base/designs.py @@ -53,12 +53,12 @@ def new(self, **kwargs) -> dict: def close(self, **kwargs) -> dict: """Close the currently open design.""" pass # pragma: no cover - + @abstractmethod def put_active(self, **kwargs) -> dict: """Activate an already opened design on the service.""" pass # pragma: no cover - + @abstractmethod def save_as(self, **kwargs) -> dict: """Create a new design.""" @@ -73,13 +73,13 @@ def download_export(self, **kwargs) -> dict: def stream_download_export(self, **kwargs) -> dict: """Download and export a design into a certain format.""" pass # pragma: no cover - + @abstractmethod def insert(self, **kwargs) -> dict: """Insert a part/component/design into an existing design.""" pass # pragma: no cover - + @abstractmethod def get_active(self, **kwargs) -> dict: """Get the active design on the service.""" - pass # pragma: no cover \ No newline at end of file + pass # pragma: no cover diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index 52ebb32d5a..621e4c1770 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -851,6 +851,7 @@ def from_grpc_update_status_to_parameter_update_status( } return status_mapping.get(update_status, ParameterUpdateStatus.UNKNOWN) + def from_design_file_format_to_grpc_part_export_format( design_file_format: "DesignFileFormat", ) -> GRPCPartExportFormat: @@ -887,4 +888,4 @@ def from_design_file_format_to_grpc_part_export_format( elif design_file_format == DesignFileFormat.DISCO: return GRPCPartExportFormat.PARTEXPORTFORMAT_DISCO else: - return None \ No newline at end of file + return None diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py index 0183be8e5b..b8edd8d224 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -41,13 +41,13 @@ class GRPCDesignsServiceV0(GRPCDesignsService): # pragma: no cover channel : grpc.Channel The gRPC channel to the server. """ - + @protect_grpc def __init__(self, channel: grpc.Channel): # noqa: D102 from ansys.api.dbu.v0.designs_pb2_grpc import DesignsStub self.stub = DesignsStub(channel) - + @protect_grpc def open(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import OpenRequest @@ -90,7 +90,7 @@ def close(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary return {} - + @protect_grpc def put_active(self, **kwargs) -> dict: # noqa: D102 # Create the request - assumes all inputs are valid and of the proper type @@ -101,7 +101,7 @@ def put_active(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary return {} - + @protect_grpc def save_as(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import SaveAsRequest @@ -120,7 +120,9 @@ def download_export(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import DownloadExportFileRequest # Create the request - assumes all inputs are valid and of the proper type - request = DownloadExportFileRequest(format=from_design_file_format_to_grpc_part_export_format(kwargs["format"])) + request = DownloadExportFileRequest( + format=from_design_file_format_to_grpc_part_export_format(kwargs["format"]) + ) # Call the gRPC service response = self.stub.DownloadExportFile(request) @@ -128,14 +130,16 @@ def download_export(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary data = bytes() data += response.data - return {"data" : data} + return {"data": data} @protect_grpc def stream_download_export(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import DownloadExportFileRequest # Create the request - assumes all inputs are valid and of the proper type - request = DownloadExportFileRequest(format=from_design_file_format_to_grpc_part_export_format(kwargs["format"])) + request = DownloadExportFileRequest( + format=from_design_file_format_to_grpc_part_export_format(kwargs["format"]) + ) # Call the gRPC service response = self.stub.StreamDownloadExportFile(request) @@ -145,8 +149,8 @@ def stream_download_export(self, **kwargs) -> dict: # noqa: D102 for elem in response: data += response.data - return {"data" : data} - + return {"data": data} + @protect_grpc def insert(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import InsertRequest @@ -159,7 +163,7 @@ def insert(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary return {} - + @protect_grpc def get_active(self, **kwargs) -> dict: # noqa: D102 from google.protobuf.empty_pb2 import Empty diff --git a/src/ansys/geometry/core/_grpc/_services/v1/designs.py b/src/ansys/geometry/core/_grpc/_services/v1/designs.py index eecb4b173b..ab9967a145 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/designs.py @@ -40,13 +40,13 @@ class GRPCDesignsServiceV1(GRPCDesignsService): # pragma: no cover channel : grpc.Channel The gRPC channel to the server. """ - + @protect_grpc def __init__(self, channel: grpc.Channel): # noqa: D102 from ansys.api.dbu.v1.designs_pb2_grpc import DesignsStub self.stub = DesignsStub(channel) - + @protect_grpc def open(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError @@ -58,11 +58,11 @@ def new(self, **kwargs) -> dict: # noqa: D102 @protect_grpc def close(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError - + @protect_grpc def put_active(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError - + @protect_grpc def save_as(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError @@ -74,13 +74,11 @@ def download_export(self, **kwargs) -> dict: # noqa: D102 @protect_grpc def stream_download_export(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError - + @protect_grpc def insert(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError - + @protect_grpc def get_active(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError - - diff --git a/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py b/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py index 0c9455ffc1..503f70529b 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/repair_tools.py @@ -27,6 +27,7 @@ from ..base.repair_tools import GRPCRepairToolsService + class GRPCRepairToolsServiceV1(GRPCRepairToolsService): # pragma: no cover """Repair tools service for gRPC communication with the Geometry server. @@ -39,9 +40,9 @@ class GRPCRepairToolsServiceV1(GRPCRepairToolsService): # pragma: no cover @protect_grpc def __init__(self, channel: grpc.Channel): # noqa: D102 from ansys.api.geometry.v1.repairtools_pb2_grpc import RepairToolsStub - + self.stub = RepairToolsStub(channel) - + @protect_grpc def find_split_edges(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index d2b84d3bc7..849f09b42e 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -30,14 +30,7 @@ import numpy as np from pint import Quantity, UndefinedUnitError -from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier, PartExportFormat -from ansys.api.dbu.v0.designs_pb2 import ( - DownloadExportFileRequest, - InsertRequest, - NewRequest, - SaveAsRequest, -) -from ansys.api.dbu.v0.designs_pb2_grpc import DesignsStub +from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier from ansys.api.geometry.v0.commands_pb2 import ( AssignMidSurfaceOffsetTypeRequest, AssignMidSurfaceThicknessRequest, @@ -109,11 +102,12 @@ class DesignFileFormat(Enum): STRIDE = "STRIDE" DISCO = "DISCO" INVALID = "INVALID" - + def __str__(self): """Represent object in string format""" return self.value + class Design(Component): """Provides for organizing geometry assemblies. From 400d52c7dff3acb09c9f71642178a56691472eb4 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 15 May 2025 21:05:13 +0200 Subject: [PATCH 03/20] feat: adding parts stub to grpc rearchitect --- .../geometry/core/_grpc/_services/_service.py | 28 ++++++++ .../core/_grpc/_services/base/parts.py | 45 ++++++++++++ .../geometry/core/_grpc/_services/v0/parts.py | 69 +++++++++++++++++++ .../geometry/core/_grpc/_services/v1/parts.py | 52 ++++++++++++++ src/ansys/geometry/core/designer/design.py | 11 ++- 5 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 src/ansys/geometry/core/_grpc/_services/base/parts.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v0/parts.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v1/parts.py diff --git a/src/ansys/geometry/core/_grpc/_services/_service.py b/src/ansys/geometry/core/_grpc/_services/_service.py index 3fbeedbe27..63c73b5736 100644 --- a/src/ansys/geometry/core/_grpc/_services/_service.py +++ b/src/ansys/geometry/core/_grpc/_services/_service.py @@ -31,6 +31,7 @@ from .base.driving_dimensions import GRPCDrivingDimensionsService from .base.measurement_tools import GRPCMeasurementToolsService from .base.named_selection import GRPCNamedSelectionService +from .base.parts import GRPCPartsService from .base.prepare_tools import GRPCPrepareToolsService from .base.repair_tools import GRPCRepairToolsService @@ -81,6 +82,7 @@ def __init__(self, channel: grpc.Channel, version: GeometryApiProtos | str | Non self._driving_dimensions = None self._measurement_tools = None self._named_selection = None + self._parts = None self._prepare_tools = None self._repair_tools = None @@ -292,6 +294,32 @@ def named_selection(self) -> GRPCNamedSelectionService: return self._named_selection + @property + def parts(self) -> GRPCPartsService: + """ + Get the parts service for the specified version. + + Returns + ------- + GRPCPartsService + The parts service for the specified version. + """ + if not self._parts: + # Import the appropriate parts service based on the version + from .v0.parts import GRPCPartsServiceV0 + from .v1.parts import GRPCPartsServiceV1 + + if self.version == GeometryApiProtos.V0: + self._parts = GRPCPartsServiceV0(self.channel) + elif self.version == GeometryApiProtos.V1: # pragma: no cover + # V1 is not implemented yet + self._parts = GRPCPartsServiceV1(self.channel) + else: # pragma: no cover + # This should never happen as the version is set in the constructor + raise ValueError(f"Unsupported version: {self.version}") + + return self._parts + @property def prepare_tools(self) -> GRPCPrepareToolsService: """ diff --git a/src/ansys/geometry/core/_grpc/_services/base/parts.py b/src/ansys/geometry/core/_grpc/_services/base/parts.py new file mode 100644 index 0000000000..fa89abd5cd --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/base/parts.py @@ -0,0 +1,45 @@ +# 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 containing the parts service implementation (abstraction layer).""" + +from abc import ABC, abstractmethod + +import grpc + + +class GRPCPartsService(ABC): + """Parts service for gRPC communication with the Geometry server. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + def __init__(self, channel: grpc.Channel): + """Initialize the GRPCPartsService class.""" + pass # pragma: no cover + + @abstractmethod + def export(self, **kwargs) -> dict: + """Export a part to a file.""" + pass diff --git a/src/ansys/geometry/core/_grpc/_services/v0/parts.py b/src/ansys/geometry/core/_grpc/_services/v0/parts.py new file mode 100644 index 0000000000..a319f341d0 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v0/parts.py @@ -0,0 +1,69 @@ +# 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 containing the parts service implementation for v0.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.parts import GRPCPartsService + + +class GRPCPartsServiceV0(GRPCPartsService): + """Parts service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + parts service. It is specifically designed for the v0 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v0.parts_pb2_grpc import PartsStub + + self.stub = PartsStub(channel) + + @protect_grpc + def export(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.parts_pb2 import ExportRequest + + from .conversions import from_design_file_format_to_grpc_part_export_format + + # Create the request - assumes all inputs are valid and of the proper type + request = ExportRequest( + format=from_design_file_format_to_grpc_part_export_format(kwargs["format"]) + ) + + # Call the gRPC service + response = self.stub.Export(request) + + # Return the response + data = bytes() + data += response.data + return { + "data": data, + } diff --git a/src/ansys/geometry/core/_grpc/_services/v1/parts.py b/src/ansys/geometry/core/_grpc/_services/v1/parts.py new file mode 100644 index 0000000000..2105272b11 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v1/parts.py @@ -0,0 +1,52 @@ +# 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 containing the parts service implementation for v1.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.parts import GRPCPartsService + + +class GRPCPartsServiceV1(GRPCPartsService): # pragma: no cover + """Parts service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + parts service. It is specifically designed for the v1 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v1.parts_pb2_grpc import PartsStub + + self.stub = PartsStub(channel) + + @protect_grpc + def export(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 849f09b42e..ca0517af4b 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -43,8 +43,6 @@ Material as GRPCMaterial, MaterialProperty as GRPCMaterialProperty, ) -from ansys.api.geometry.v0.parts_pb2 import ExportRequest -from ansys.api.geometry.v0.parts_pb2_grpc import PartsStub from ansys.geometry.core.connection.backend import BackendType from ansys.geometry.core.connection.conversions import ( grpc_curve_to_curve, @@ -104,7 +102,7 @@ class DesignFileFormat(Enum): INVALID = "INVALID" def __str__(self): - """Represent object in string format""" + """Represent object in string format.""" return self.value @@ -139,7 +137,6 @@ def __init__(self, name: str, modeler: Modeler, read_existing_design: bool = Fal # Initialize the stubs needed self._commands_stub = CommandsStub(self._grpc_client.channel) self._materials_stub = MaterialsStub(self._grpc_client.channel) - self._parts_stub = PartsStub(self._grpc_client.channel) # Initialize needed instance variables self._materials = [] @@ -329,9 +326,9 @@ def __export_and_download_legacy(self, format: DesignFileFormat) -> bytes: """ # Process response self._grpc_client.log.debug(f"Requesting design download in {format} format.") - received_bytes = bytes() if format is DesignFileFormat.SCDOCX: response = self._commands_stub.DownloadFile(Empty()) + received_bytes = bytes() received_bytes += response.data elif format in [ DesignFileFormat.PARASOLID_TEXT, @@ -341,8 +338,8 @@ def __export_and_download_legacy(self, format: DesignFileFormat) -> bytes: DesignFileFormat.IGES, DesignFileFormat.PMDB, ]: - response = self._parts_stub.Export(ExportRequest(format=format[1])) - received_bytes += response.data + response = self._grpc_client.services.parts.export(format=format) + received_bytes = response.get("data") else: self._grpc_client.log.warning( f"{format} format requested is not supported. Ignoring download request." From 305c07846c23d6c1de389245757a4730ddf6facb Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 15 May 2025 19:13:35 +0000 Subject: [PATCH 04/20] chore: adding changelog file 1988.added.md [dependabot-skip] --- doc/changelog.d/1988.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/1988.added.md diff --git a/doc/changelog.d/1988.added.md b/doc/changelog.d/1988.added.md new file mode 100644 index 0000000000..594d6ed9c1 --- /dev/null +++ b/doc/changelog.d/1988.added.md @@ -0,0 +1 @@ +grpc reachitecture - several modules \ No newline at end of file From c9433124c7b0557d63ed9ee1f399cb6894150663 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Fri, 16 May 2025 03:34:39 +0200 Subject: [PATCH 05/20] chore: no cover statements --- .../core/_grpc/_services/base/admin.py | 10 +-- .../core/_grpc/_services/base/bodies.py | 70 +++++++++---------- .../_services/base/coordinate_systems.py | 6 +- .../_grpc/_services/base/dbuapplication.py | 6 +- .../core/_grpc/_services/base/designs.py | 22 +++--- .../_services/base/driving_dimensions.py | 8 +-- .../_grpc/_services/base/measurement_tools.py | 6 +- .../_grpc/_services/base/named_selection.py | 6 +- .../core/_grpc/_services/base/parts.py | 4 +- .../_grpc/_services/base/prepare_tools.py | 20 +++--- .../core/_grpc/_services/base/repair_tools.py | 34 ++++----- 11 files changed, 96 insertions(+), 96 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/base/admin.py b/src/ansys/geometry/core/_grpc/_services/base/admin.py index 9c862ca379..51aefd71fe 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/admin.py +++ b/src/ansys/geometry/core/_grpc/_services/base/admin.py @@ -26,7 +26,7 @@ import grpc -class GRPCAdminService(ABC): +class GRPCAdminService(ABC): # pragma: no cover """Admin service for gRPC communication with the Geometry server. Parameters @@ -37,19 +37,19 @@ class GRPCAdminService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCAdminService class.""" - pass # pragma: no cover + pass @abstractmethod def get_backend(self, **kwargs) -> dict: """Get server information.""" - pass # pragma: no cover + pass @abstractmethod def get_logs(self, **kwargs) -> dict: """Get server logs.""" - pass # pragma: no cover + pass @abstractmethod def get_service_status(self, **kwargs) -> dict: """Get server status (i.e. healthy or not).""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/bodies.py b/src/ansys/geometry/core/_grpc/_services/base/bodies.py index 12e3dbb566..f5c690e1e9 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/bodies.py +++ b/src/ansys/geometry/core/_grpc/_services/base/bodies.py @@ -26,7 +26,7 @@ import grpc -class GRPCBodyService(ABC): +class GRPCBodyService(ABC): # pragma: no cover """Body service for gRPC communication with the Geometry server. Parameters @@ -37,169 +37,169 @@ class GRPCBodyService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCBodyService class.""" - pass # pragma: no cover + pass @abstractmethod def create_sphere_body(self, **kwargs) -> dict: """Create a sphere body.""" - pass # pragma: no cover + pass @abstractmethod def create_extruded_body(self, **kwargs) -> dict: """Create an extruded body.""" - pass # pragma: no cover + pass @abstractmethod def create_sweeping_profile_body(self, **kwargs) -> dict: """Create a sweeping profile body.""" - pass # pragma: no cover + pass @abstractmethod def create_sweeping_chain(self, **kwargs) -> dict: """Create a sweeping chain.""" - pass # pragma: no cover + pass @abstractmethod def create_extruded_body_from_face_profile(self, **kwargs) -> dict: """Create an extruded body from a face profile.""" - pass # pragma: no cover + pass @abstractmethod def create_extruded_body_from_loft_profiles(self, **kwargs) -> dict: """Create an extruded body from loft profiles.""" - pass # pragma: no cover + pass @abstractmethod def create_planar_body(self, **kwargs) -> dict: """Create a planar body.""" - pass # pragma: no cover + pass @abstractmethod def create_body_from_face(self, **kwargs) -> dict: """Create a body from a face.""" - pass # pragma: no cover + pass @abstractmethod def create_surface_body(self, **kwargs) -> dict: """Create a surface body.""" - pass # pragma: no cover + pass @abstractmethod def create_surface_body_from_trimmed_curves(self, **kwargs) -> dict: """Create a surface body from trimmed curves.""" - pass # pragma: no cover + pass @abstractmethod def translate(self, **kwargs) -> dict: """Translate a body.""" - pass # pragma: no cover + pass @abstractmethod def delete(self, **kwargs) -> dict: """Delete a body.""" - pass # pragma: no cover + pass @abstractmethod def is_suppressed(self, **kwargs) -> dict: """Check if a body is suppressed.""" - pass # pragma: no cover + pass @abstractmethod def get_color(self, **kwargs) -> dict: """Get the color of a body.""" - pass # pragma: no cover + pass @abstractmethod def get_faces(self, **kwargs) -> dict: """Get the faces of a body.""" - pass # pragma: no cover + pass @abstractmethod def get_edges(self, **kwargs) -> dict: """Get the edges of a body.""" - pass # pragma: no cover + pass @abstractmethod def get_volume(self, **kwargs) -> dict: """Get the volume of a body.""" - pass # pragma: no cover + pass @abstractmethod def get_bounding_box(self, **kwargs) -> dict: """Get the bounding box of a body.""" - pass # pragma: no cover + pass @abstractmethod def set_assigned_material(self, **kwargs) -> dict: """Set the assigned material of a body.""" - pass # pragma: no cover + pass @abstractmethod def get_assigned_material(self, **kwargs) -> dict: """Get the assigned material of a body.""" - pass # pragma: no cover + pass @abstractmethod def set_name(self, **kwargs) -> dict: """Set the name of a body.""" - pass # pragma: no cover + pass @abstractmethod def set_fill_style(self, **kwargs) -> dict: """Set the fill style of a body.""" - pass # pragma: no cover + pass @abstractmethod def set_suppressed(self, **kwargs) -> dict: """Set the suppressed state of a body.""" - pass # pragma: no cover + pass @abstractmethod def set_color(self, **kwargs) -> dict: """Set the color of a body.""" - pass # pragma: no cover + pass @abstractmethod def rotate(self, **kwargs) -> dict: """Rotate a body.""" - pass # pragma: no cover + pass @abstractmethod def scale(self, **kwargs) -> dict: """Scale a body.""" - pass # pragma: no cover + pass @abstractmethod def mirror(self, **kwargs) -> dict: """Mirror a body.""" - pass # pragma: no cover + pass @abstractmethod def map(self, **kwargs) -> dict: """Map a body.""" - pass # pragma: no cover + pass @abstractmethod def get_collision(self, **kwargs) -> dict: """Get the collision of a body.""" - pass # pragma: no cover + pass @abstractmethod def copy(self, **kwargs) -> dict: """Copy a body.""" - pass # pragma: no cover + pass @abstractmethod def get_tesellation(self, **kwargs) -> dict: """Get the tessellation of a body.""" - pass # pragma: no cover + pass @abstractmethod def get_tesellation_with_options(self, **kwargs) -> dict: """Get the tessellation of a body with options.""" - pass # pragma: no cover + pass @abstractmethod def boolean(self, **kwargs) -> dict: """Boolean operation.""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/coordinate_systems.py b/src/ansys/geometry/core/_grpc/_services/base/coordinate_systems.py index 5e35711927..086725af59 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/coordinate_systems.py +++ b/src/ansys/geometry/core/_grpc/_services/base/coordinate_systems.py @@ -26,7 +26,7 @@ import grpc -class GRPCCoordinateSystemService(ABC): +class GRPCCoordinateSystemService(ABC): # pragma: no cover """Coordinate systems service for gRPC communication with the Geometry server. Parameters @@ -37,9 +37,9 @@ class GRPCCoordinateSystemService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCCoordinateSystemService class.""" - pass # pragma: no cover + pass @abstractmethod def create(self, **kwargs) -> dict: """Create a coordinate system.""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/dbuapplication.py b/src/ansys/geometry/core/_grpc/_services/base/dbuapplication.py index a8b366f8b2..6c0aaa5360 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/dbuapplication.py +++ b/src/ansys/geometry/core/_grpc/_services/base/dbuapplication.py @@ -26,7 +26,7 @@ import grpc -class GRPCDbuApplicationService(ABC): +class GRPCDbuApplicationService(ABC): # pragma: no cover """DBU Application service for gRPC communication with the Geometry server. Parameters @@ -37,9 +37,9 @@ class GRPCDbuApplicationService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCDbuApplicationService class.""" - pass # pragma: no cover + pass @abstractmethod def run_script(self, **kwargs) -> dict: """Run a Scripting API script.""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/designs.py b/src/ansys/geometry/core/_grpc/_services/base/designs.py index dbc3ffd2a6..3c8a810432 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/base/designs.py @@ -26,7 +26,7 @@ import grpc -class GRPCDesignsService(ABC): +class GRPCDesignsService(ABC): # pragma: no cover """Designs service for gRPC communication with the Geometry server. Parameters @@ -37,49 +37,49 @@ class GRPCDesignsService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCDesignsService class.""" - pass # pragma: no cover + pass @abstractmethod def open(self, **kwargs) -> dict: """Open a design on the service.""" - pass # pragma: no cover + pass @abstractmethod def new(self, **kwargs) -> dict: """Create a new design.""" - pass # pragma: no cover + pass @abstractmethod def close(self, **kwargs) -> dict: """Close the currently open design.""" - pass # pragma: no cover + pass @abstractmethod def put_active(self, **kwargs) -> dict: """Activate an already opened design on the service.""" - pass # pragma: no cover + pass @abstractmethod def save_as(self, **kwargs) -> dict: """Create a new design.""" - pass # pragma: no cover + pass @abstractmethod def download_export(self, **kwargs) -> dict: """Download and export a design into a certain format.""" - pass # pragma: no cover + pass @abstractmethod def stream_download_export(self, **kwargs) -> dict: """Download and export a design into a certain format.""" - pass # pragma: no cover + pass @abstractmethod def insert(self, **kwargs) -> dict: """Insert a part/component/design into an existing design.""" - pass # pragma: no cover + pass @abstractmethod def get_active(self, **kwargs) -> dict: """Get the active design on the service.""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/driving_dimensions.py b/src/ansys/geometry/core/_grpc/_services/base/driving_dimensions.py index 1275658b48..17243bd1bb 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/driving_dimensions.py +++ b/src/ansys/geometry/core/_grpc/_services/base/driving_dimensions.py @@ -26,7 +26,7 @@ import grpc -class GRPCDrivingDimensionsService(ABC): +class GRPCDrivingDimensionsService(ABC): # pragma: no cover """Driving Dimension service for gRPC communication with the Geometry server. Parameters @@ -37,14 +37,14 @@ class GRPCDrivingDimensionsService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCDrivingDimensionsService class.""" - pass # pragma: no cover + pass @abstractmethod def get_all_parameters(self, **kwargs) -> dict: """Get driving dimensions.""" - pass # pragma: no cover + pass @abstractmethod def set_parameter(self, **kwargs) -> dict: """Set driving dimensions.""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/measurement_tools.py b/src/ansys/geometry/core/_grpc/_services/base/measurement_tools.py index bf955d3d39..509d539a2d 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/measurement_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/base/measurement_tools.py @@ -26,7 +26,7 @@ import grpc -class GRPCMeasurementToolsService(ABC): +class GRPCMeasurementToolsService(ABC): # pragma: no cover """Measurement tools service for gRPC communication with the Geometry server. Parameters @@ -37,9 +37,9 @@ class GRPCMeasurementToolsService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCMeasurementToolsService class.""" - pass # pragma: no cover + pass @abstractmethod def min_distance_between_objects(self, **kwargs) -> dict: """Calculate the minimum distance between two objects.""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/named_selection.py b/src/ansys/geometry/core/_grpc/_services/base/named_selection.py index 929939f7f7..c794bd723d 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/named_selection.py +++ b/src/ansys/geometry/core/_grpc/_services/base/named_selection.py @@ -26,7 +26,7 @@ import grpc -class GRPCNamedSelectionService(ABC): +class GRPCNamedSelectionService(ABC): # pragma: no cover """Named Selection service for gRPC communication with the Geometry server. Parameters @@ -37,12 +37,12 @@ class GRPCNamedSelectionService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCNamedSelectionService class.""" - pass # pragma: no cover + pass @abstractmethod def get_named_selection(self, **kwargs) -> dict: """Get the named selection by its id.""" - pass # pragma: no cover + pass @abstractmethod def create_named_selection(self, **kwargs) -> dict: diff --git a/src/ansys/geometry/core/_grpc/_services/base/parts.py b/src/ansys/geometry/core/_grpc/_services/base/parts.py index fa89abd5cd..7c85f7f70c 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/parts.py +++ b/src/ansys/geometry/core/_grpc/_services/base/parts.py @@ -26,7 +26,7 @@ import grpc -class GRPCPartsService(ABC): +class GRPCPartsService(ABC): # pragma: no cover """Parts service for gRPC communication with the Geometry server. Parameters @@ -37,7 +37,7 @@ class GRPCPartsService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCPartsService class.""" - pass # pragma: no cover + pass @abstractmethod def export(self, **kwargs) -> dict: diff --git a/src/ansys/geometry/core/_grpc/_services/base/prepare_tools.py b/src/ansys/geometry/core/_grpc/_services/base/prepare_tools.py index 40ca25dd44..a3b9f03136 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/prepare_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/base/prepare_tools.py @@ -26,7 +26,7 @@ import grpc -class GRPCPrepareToolsService(ABC): +class GRPCPrepareToolsService(ABC): # pragma: no cover """Prepare tools service for gRPC communication with the Geometry server. Parameters @@ -37,44 +37,44 @@ class GRPCPrepareToolsService(ABC): def __init__(self, channel: grpc.Channel): """Initialize the GRPCPrepareToolsService class.""" - pass # pragma: no cover + pass @abstractmethod def extract_volume_from_faces(self, **kwargs) -> dict: """Extract a volume from input faces.""" - pass # pragma: no cover + pass @abstractmethod def extract_volume_from_edge_loops(self, **kwargs) -> dict: """Extract a volume from input edge loop.""" - pass # pragma: no cover + pass @abstractmethod def remove_rounds(self, **kwargs) -> dict: """Remove rounds from geometry.""" - pass # pragma: no cover + pass @abstractmethod def share_topology(self, **kwargs) -> dict: """Share topology between the given bodies.""" - pass # pragma: no cover + pass @abstractmethod def enhanced_share_topology(self, **kwargs) -> dict: """Share topology between the given bodies.""" - pass # pragma: no cover + pass @abstractmethod def find_logos(self, **kwargs) -> dict: """Detect logos in geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_and_remove_logos(self, **kwargs) -> dict: """Detect and remove logos in geometry.""" - pass # pragma: no cover + pass @abstractmethod def remove_logo(self, **kwargs) -> dict: """Remove logos in geometry.""" - pass # pragma: no cover + pass diff --git a/src/ansys/geometry/core/_grpc/_services/base/repair_tools.py b/src/ansys/geometry/core/_grpc/_services/base/repair_tools.py index f9fbd791cd..cbbf7b90d3 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/repair_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/base/repair_tools.py @@ -31,7 +31,7 @@ import grpc -class GRPCRepairToolsService(ABC): +class GRPCRepairToolsService(ABC): # pragma: no cover """Abstract base class for gRPC-based repair tools service. Parameters @@ -46,79 +46,79 @@ def __init__(self, channel: grpc.Channel): @abstractmethod def find_split_edges(self, **kwargs) -> dict: """Identify split edges in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_extra_edges(self, **kwargs) -> dict: """Identify extra edges in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_inexact_edges(self, **kwargs) -> dict: """Identify inexact edges in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_short_edges(self, **kwargs) -> dict: """Identify short edges in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_duplicate_faces(self, **kwargs) -> dict: """Identify duplicate faces in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_missing_faces(self, **kwargs) -> dict: """Identify missing faces in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_small_faces(self, **kwargs) -> dict: """Identify small faces in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_stitch_faces(self, **kwargs) -> dict: """Identify faces that can be stitched together in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_simplify(self, **kwargs) -> dict: """Identify areas in the geometry that can be simplified.""" - pass # pragma: no cover + pass @abstractmethod def find_interferences(self, **kwargs) -> dict: """Identify interferences in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_and_fix_short_edges(self, **kwargs) -> dict: """Identify and fix short edges in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_and_fix_extra_edges(self, **kwargs) -> dict: """Identify and fix extra edges in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_and_fix_split_edges(self, **kwargs) -> dict: """Identify and fix split edges in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def find_and_fix_simplify(self, **kwargs) -> dict: """Identify and simplify areas in the geometry.""" - pass # pragma: no cover + pass @abstractmethod def inspect_geometry(self, **kwargs) -> dict: """Inspect the geometry for issues.""" - pass # pragma: no cover + pass @abstractmethod def repair_geometry(self, **kwargs) -> dict: """Repair the geometry by addressing identified issues.""" - pass # pragma: no cover + pass From a7a7ed38cca83c6c7a6a465fc689c9bfea324889 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Fri, 16 May 2025 03:49:31 +0200 Subject: [PATCH 06/20] feat: change materials stub --- .../geometry/core/_grpc/_services/_service.py | 28 ++++++++ .../core/_grpc/_services/base/materials.py | 45 +++++++++++++ .../core/_grpc/_services/v0/conversions.py | 29 +++++++++ .../core/_grpc/_services/v0/materials.py | 65 +++++++++++++++++++ .../core/_grpc/_services/v1/materials.py | 52 +++++++++++++++ src/ansys/geometry/core/designer/design.py | 27 +------- 6 files changed, 220 insertions(+), 26 deletions(-) create mode 100644 src/ansys/geometry/core/_grpc/_services/base/materials.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v0/materials.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v1/materials.py diff --git a/src/ansys/geometry/core/_grpc/_services/_service.py b/src/ansys/geometry/core/_grpc/_services/_service.py index 63c73b5736..19e8fae3c2 100644 --- a/src/ansys/geometry/core/_grpc/_services/_service.py +++ b/src/ansys/geometry/core/_grpc/_services/_service.py @@ -29,6 +29,7 @@ from .base.dbuapplication import GRPCDbuApplicationService from .base.designs import GRPCDesignsService from .base.driving_dimensions import GRPCDrivingDimensionsService +from .base.materials import GRPCMaterialsService from .base.measurement_tools import GRPCMeasurementToolsService from .base.named_selection import GRPCNamedSelectionService from .base.parts import GRPCPartsService @@ -80,6 +81,7 @@ def __init__(self, channel: grpc.Channel, version: GeometryApiProtos | str | Non self._dbu_application = None self._designs = None self._driving_dimensions = None + self._materials = None self._measurement_tools = None self._named_selection = None self._parts = None @@ -242,6 +244,32 @@ def driving_dimensions(self) -> GRPCDrivingDimensionsService: return self._driving_dimensions + @property + def materials(self) -> GRPCMaterialsService: + """ + Get the materials service for the specified version. + + Returns + ------- + GRPCMaterialsService + The materials service for the specified version. + """ + if not self._materials: + # Import the appropriate materials service based on the version + from .v0.materials import GRPCMaterialsServiceV0 + from .v1.materials import GRPCMaterialsServiceV1 + + if self.version == GeometryApiProtos.V0: + self._materials = GRPCMaterialsServiceV0(self.channel) + elif self.version == GeometryApiProtos.V1: + # V1 is not implemented yet + self._materials = GRPCMaterialsServiceV1(self.channel) + else: # pragma: no cover + # This should never happen as the version is set in the constructor + raise ValueError(f"Unsupported version: {self.version}") + + return self._materials + @property def measurement_tools(self) -> GRPCMeasurementToolsService: """ diff --git a/src/ansys/geometry/core/_grpc/_services/base/materials.py b/src/ansys/geometry/core/_grpc/_services/base/materials.py new file mode 100644 index 0000000000..bceb66e8fc --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/base/materials.py @@ -0,0 +1,45 @@ +# 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 containing the materials service implementation (abstraction layer).""" + +from abc import ABC, abstractmethod + +import grpc + + +class GRPCMaterialsService(ABC): # pragma: no cover + """Materials service for gRPC communication with the Geometry server. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + def __init__(self, channel: grpc.Channel): + """Initialize the GRPCMaterialsService class.""" + pass + + @abstractmethod + def add_material(self, **kwargs) -> dict: + """Add material to the service design.""" + pass diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index 621e4c1770..d57868b386 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -889,3 +889,32 @@ def from_design_file_format_to_grpc_part_export_format( return GRPCPartExportFormat.PARTEXPORTFORMAT_DISCO else: return None + + +def from_material_to_grpc_material( + material: "Material", +) -> GRPCMaterial: + """Convert a ``Material`` class to a material gRPC message. + + Parameters + ---------- + material : Material + Source material data. + + Returns + ------- + GRPCMaterial + Geometry service gRPC material message. + """ + return GRPCMaterial( + name=material.name, + material_properties=[ + GRPCMaterialProperty( + id=property.type.value, + display_name=property.name, + value=property.quantity.m, + units=format(property.quantity.units), + ) + for property in material.properties.values() + ], + ) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/materials.py b/src/ansys/geometry/core/_grpc/_services/v0/materials.py new file mode 100644 index 0000000000..631bdb6251 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v0/materials.py @@ -0,0 +1,65 @@ +# 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 containing the materials service implementation for v0.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.materials import GRPCMaterialsService + + +class GRPCMaterialsServiceV0(GRPCMaterialsService): + """Materials service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + materials service. It is specifically designed for the v0 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v0.materials_pb2_grpc import MaterialsStub + + self.stub = MaterialsStub(channel) + + @protect_grpc + def add_material(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.materials_pb2 import AddToDocumentRequest + + from .conversions import from_material_to_grpc_material + + # Create the request - assumes all inputs are valid and of the proper type + request = AddToDocumentRequest( + material=from_material_to_grpc_material(kwargs["material"]), + ) + + # Call the gRPC service + _ = self.stub.AddToDocument(request=request) + + # Convert the response to a dictionary + return {} diff --git a/src/ansys/geometry/core/_grpc/_services/v1/materials.py b/src/ansys/geometry/core/_grpc/_services/v1/materials.py new file mode 100644 index 0000000000..4b54d626b6 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v1/materials.py @@ -0,0 +1,52 @@ +# 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 containing the materials service implementation for v1.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.materials import GRPCMaterialsService + + +class GRPCMaterialsServiceV1(GRPCMaterialsService): # pragma: no cover + """Materials service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + materials service. It is specifically designed for the v1 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v1.materials_pb2_grpc import MaterialsStub + + self.stub = MaterialsStub(channel) + + @protect_grpc + def add_material(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index ca0517af4b..0c2f5ccf6b 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -37,12 +37,6 @@ CreateBeamCircularProfileRequest, ) from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub -from ansys.api.geometry.v0.materials_pb2 import AddToDocumentRequest -from ansys.api.geometry.v0.materials_pb2_grpc import MaterialsStub -from ansys.api.geometry.v0.models_pb2 import ( - Material as GRPCMaterial, - MaterialProperty as GRPCMaterialProperty, -) from ansys.geometry.core.connection.backend import BackendType from ansys.geometry.core.connection.conversions import ( grpc_curve_to_curve, @@ -136,7 +130,6 @@ def __init__(self, name: str, modeler: Modeler, read_existing_design: bool = Fal # Initialize the stubs needed self._commands_stub = CommandsStub(self._grpc_client.channel) - self._materials_stub = MaterialsStub(self._grpc_client.channel) # Initialize needed instance variables self._materials = [] @@ -230,26 +223,8 @@ def add_material(self, material: Material) -> None: material : Material Material to add. """ - # TODO: Add design id to the request - # https://github.com/ansys/pyansys-geometry/issues/1319 - self._materials_stub.AddToDocument( - AddToDocumentRequest( - material=GRPCMaterial( - name=material.name, - material_properties=[ - GRPCMaterialProperty( - id=property.type.value, - display_name=property.name, - value=property.quantity.m, - units=format(property.quantity.units), - ) - for property in material.properties.values() - ], - ) - ) - ) + self._grpc_client.services.materials.add_material(material=material) self._materials.append(material) - self._grpc_client.log.debug(f"Material {material.name} is successfully added to design.") @check_input_types From 9e84a48771b48c6e9e0048986dd1ee7a5cc71810 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Sun, 18 May 2025 12:54:39 +0200 Subject: [PATCH 07/20] feat: add edges stub --- .../geometry/core/_grpc/_services/_service.py | 28 ++++ .../core/_grpc/_services/base/edges.py | 75 +++++++++ .../core/_grpc/_services/v0/conversions.py | 45 +++++ .../geometry/core/_grpc/_services/v0/edges.py | 156 ++++++++++++++++++ .../geometry/core/_grpc/_services/v1/edges.py | 76 +++++++++ src/ansys/geometry/core/designer/edge.py | 69 +++----- src/ansys/geometry/core/designer/face.py | 40 +++-- 7 files changed, 426 insertions(+), 63 deletions(-) create mode 100644 src/ansys/geometry/core/_grpc/_services/base/edges.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v0/edges.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v1/edges.py diff --git a/src/ansys/geometry/core/_grpc/_services/_service.py b/src/ansys/geometry/core/_grpc/_services/_service.py index 19e8fae3c2..9644653c17 100644 --- a/src/ansys/geometry/core/_grpc/_services/_service.py +++ b/src/ansys/geometry/core/_grpc/_services/_service.py @@ -29,6 +29,7 @@ from .base.dbuapplication import GRPCDbuApplicationService from .base.designs import GRPCDesignsService from .base.driving_dimensions import GRPCDrivingDimensionsService +from .base.edges import GRPCEdgesService from .base.materials import GRPCMaterialsService from .base.measurement_tools import GRPCMeasurementToolsService from .base.named_selection import GRPCNamedSelectionService @@ -81,6 +82,7 @@ def __init__(self, channel: grpc.Channel, version: GeometryApiProtos | str | Non self._dbu_application = None self._designs = None self._driving_dimensions = None + self._edges = None self._materials = None self._measurement_tools = None self._named_selection = None @@ -244,6 +246,32 @@ def driving_dimensions(self) -> GRPCDrivingDimensionsService: return self._driving_dimensions + @property + def edges(self) -> GRPCEdgesService: + """ + Get the edges service for the specified version. + + Returns + ------- + GRPCEdgesService + The edges service for the specified version. + """ + if not self._edges: + # Import the appropriate edges service based on the version + from .v0.edges import GRPCEdgesServiceV0 + from .v1.edges import GRPCEdgesServiceV1 + + if self.version == GeometryApiProtos.V0: + self._edges = GRPCEdgesServiceV0(self.channel) + elif self.version == GeometryApiProtos.V1: # pragma: no cover + # V1 is not implemented yet + self._edges = GRPCEdgesServiceV1(self.channel) + else: # pragma: no cover + # This should never happen as the version is set in the constructor + raise ValueError(f"Unsupported version: {self.version}") + + return self._edges + @property def materials(self) -> GRPCMaterialsService: """ diff --git a/src/ansys/geometry/core/_grpc/_services/base/edges.py b/src/ansys/geometry/core/_grpc/_services/base/edges.py new file mode 100644 index 0000000000..0eedffae28 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/base/edges.py @@ -0,0 +1,75 @@ +# 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 containing the edges service implementation (abstraction layer).""" + +from abc import ABC, abstractmethod + +import grpc + + +class GRPCEdgesService(ABC): # pragma: no cover + """Edges service for gRPC communication with the Geometry server. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + def __init__(self, channel: grpc.Channel): + """Initialize the GRPCEdgesService class.""" + pass + + @abstractmethod + def get_edge(self, **kwargs) -> dict: + """Get edge.""" + pass + + @abstractmethod + def get_curve(self, **kwargs) -> dict: + """Get curve information for the edge.""" + pass + + @abstractmethod + def get_start_and_end_points(self, **kwargs) -> dict: + """Get start and end points for the edge.""" + pass + + @abstractmethod + def get_length(self, **kwargs) -> dict: + """Get the length of the edge.""" + pass + + @abstractmethod + def get_interval(self, **kwargs) -> dict: + """Get the interval of the edge.""" + pass + + @abstractmethod + def get_faces(self, **kwargs) -> dict: + """Get the faces that are connected to the edge.""" + pass + + @abstractmethod + def get_bounding_box(self, **kwargs) -> dict: + """Get the bounding box of the edge.""" + pass diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index d57868b386..498f50aaed 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -650,6 +650,51 @@ def from_curve_to_grpc_curve(curve: "Curve") -> GRPCCurveGeometry: return grpc_curve +def from_grpc_curve_to_curve(curve: GRPCCurveGeometry) -> "Curve": + """Convert a curve gRPC message to a ``Curve``. + + Parameters + ---------- + curve : GRPCCurve + Geometry service gRPC curve message. + + Returns + ------- + Curve + Resulting converted curve. + """ + from ansys.geometry.core.shapes.curves.circle import Circle + from ansys.geometry.core.shapes.curves.ellipse import Ellipse + from ansys.geometry.core.shapes.curves.line import Line + + origin = Point3D([curve.origin.x, curve.origin.y, curve.origin.z]) + try: + reference = UnitVector3D([curve.reference.x, curve.reference.y, curve.reference.z]) + axis = UnitVector3D([curve.axis.x, curve.axis.y, curve.axis.z]) + except ValueError: + # curve will be a line + pass + if curve.radius != 0: + result = Circle(origin, curve.radius, reference, axis) + elif curve.major_radius != 0 and curve.minor_radius != 0: + result = Ellipse(origin, curve.major_radius, curve.minor_radius, reference, axis) + elif curve.direction is not None: + result = Line( + origin, + UnitVector3D( + [ + curve.direction.x, + curve.direction.y, + curve.direction.z, + ] + ), + ) + else: + result = None + + return result + + def from_trimmed_surface_to_grpc_trimmed_surface( trimmed_surface: "TrimmedSurface", ) -> GRPCTrimmedSurface: diff --git a/src/ansys/geometry/core/_grpc/_services/v0/edges.py b/src/ansys/geometry/core/_grpc/_services/v0/edges.py new file mode 100644 index 0000000000..3ef02576d3 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v0/edges.py @@ -0,0 +1,156 @@ +# 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 containing the edges service implementation for v0.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.conversions import to_distance +from ..base.edges import GRPCEdgesService +from .conversions import build_grpc_id, from_grpc_point_to_point3d + + +class GRPCEdgesServiceV0(GRPCEdgesService): + """Edges service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + edges service. It is specifically designed for the v0 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v0.edges_pb2_grpc import EdgesStub + + self.stub = EdgesStub(channel) + + @protect_grpc + def get_edge(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.Get(request=request) + + # Return the response - formatted as a dictionary + return { + "edge_id": response.id, + "edge_curve_type": response.curve_type, + "edge_is_reversed": response.is_reversed, + } + + @protect_grpc + def get_curve(self, **kwargs) -> dict: # noqa: D102 + from .conversions import from_grpc_curve_to_curve + + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetCurve(request=request) + + # Return the response - formatted as a dictionary + return { + "curve": from_grpc_curve_to_curve(response.curve), + } + + @protect_grpc + def get_start_and_end_points(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetStartAndEndPoints(request=request) + + # Return the response - formatted as a dictionary + return { + "start": from_grpc_point_to_point3d(response.start), + "end": from_grpc_point_to_point3d(response.end), + } + + @protect_grpc + def get_length(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetLength(request=request) + + # Return the response - formatted as a dictionary + return { + "length": to_distance(response.length), + } + + @protect_grpc + def get_interval(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetInterval(request=request) + + # Return the response - formatted as a dictionary + return { + "start": response.start, + "end": response.end, + } + + @protect_grpc + def get_faces(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetFaces(request=request) + + # Return the response - formatted as a dictionary + return { + "faces": [ + { + "id": response.id, + "surface_type": response.surface_type, + "is_reversed": response.is_reversed, + } + for face in response.faces + ], + } + + @protect_grpc + def get_bounding_box(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetBoundingBox(request=request) + + # Return the response - formatted as a dictionary + return { + "min_corner": from_grpc_point_to_point3d(response.min), + "max_corner": from_grpc_point_to_point3d(response.max), + "center": from_grpc_point_to_point3d(response.center), + } diff --git a/src/ansys/geometry/core/_grpc/_services/v1/edges.py b/src/ansys/geometry/core/_grpc/_services/v1/edges.py new file mode 100644 index 0000000000..a3289ee347 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v1/edges.py @@ -0,0 +1,76 @@ +# 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 containing the edges service implementation for v1.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.edges import GRPCEdgesService + + +class GRPCEdgesServiceV1(GRPCEdgesService): # pragma: no cover + """Edges service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + edges service. It is specifically designed for the v1 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v1.edges_pb2_grpc import EdgesStub + + self.stub = EdgesStub(channel) + + @protect_grpc + def get_edge(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError + + @protect_grpc + def get_curve(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError + + @protect_grpc + def get_start_and_end_points(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError + + @protect_grpc + def get_length(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError + + @protect_grpc + def get_interval(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError + + @protect_grpc + def get_faces(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError + + @protect_grpc + def get_bounding_box(self, **kwargs) -> dict: # noqa: D102 + return NotImplementedError diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index 6f610aa2a8..ffb5622ce8 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -29,12 +29,10 @@ from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier from ansys.api.geometry.v0.edges_pb2_grpc import EdgesStub from ansys.geometry.core.connection.client import GrpcClient -from ansys.geometry.core.connection.conversions import grpc_curve_to_curve, grpc_point_to_point3d -from ansys.geometry.core.errors import GeometryRuntimeError, protect_grpc +from ansys.geometry.core.errors import GeometryRuntimeError from ansys.geometry.core.math.bbox import BoundingBox from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version -from ansys.geometry.core.misc.measurements import DEFAULT_UNITS from ansys.geometry.core.shapes.curves.trimmed_curve import ReversedTrimmedCurve, TrimmedCurve from ansys.geometry.core.shapes.parameterization import Interval @@ -112,7 +110,6 @@ def is_reversed(self) -> bool: return self._is_reversed @property - @protect_grpc @ensure_design_is_active @min_backend_version(24, 2, 0) def shape(self) -> TrimmedCurve: @@ -124,33 +121,25 @@ def shape(self) -> TrimmedCurve: """ if self._shape is None: self._grpc_client.log.debug("Requesting edge properties from server.") - response = self._edges_stub.GetCurve(self._grpc_id) - geometry = grpc_curve_to_curve(response) + geometry = self._grpc_client.services.edges.get_curve(id=self._id).get("curve") - response = self._edges_stub.GetStartAndEndPoints(self._grpc_id) - start = Point3D( - [response.start.x, response.start.y, response.start.z], - unit=DEFAULT_UNITS.SERVER_LENGTH, - ) - end = Point3D( - [response.end.x, response.end.y, response.end.z], unit=DEFAULT_UNITS.SERVER_LENGTH - ) + response = self._grpc_client.services.edges.get_start_and_end_points(id=self._id) + start = response.get("start") + end = response.get("end") - response = self._edges_stub.GetLength(self._grpc_id) - length = Quantity(response.length, DEFAULT_UNITS.SERVER_LENGTH) + length = self._grpc_client.services.edges.get_length(id=self._id).get("length") - response = self._edges_stub.GetInterval(self._grpc_id) - interval = Interval(response.start, response.end) + response = self._grpc_client.services.edges.get_interval(id=self._id) + interval = Interval(response.get("start"), response.get("end")) self._shape = ( - ReversedTrimmedCurve(geometry, start, end, interval, length) + ReversedTrimmedCurve(geometry, start, end, interval, length.value) if self.is_reversed - else TrimmedCurve(geometry, start, end, interval, length) + else TrimmedCurve(geometry, start, end, interval, length.value) ) return self._shape @property - @protect_grpc @ensure_design_is_active def length(self) -> Quantity: """Calculated length of the edge.""" @@ -159,8 +148,7 @@ def length(self) -> Quantity: except GeometryRuntimeError: # pragma: no cover # Only for versions earlier than 24.2.0 (before the introduction of the shape property) self._grpc_client.log.debug("Requesting edge length from server.") - length_response = self._edges_stub.GetLength(self._grpc_id) - return Quantity(length_response.length, DEFAULT_UNITS.SERVER_LENGTH) + return self._grpc_client.services.edges.get_length(id=self._id).get("length").value @property def curve_type(self) -> CurveType: @@ -168,27 +156,25 @@ def curve_type(self) -> CurveType: return self._curve_type @property - @protect_grpc @ensure_design_is_active def faces(self) -> list["Face"]: """Faces that contain the edge.""" from ansys.geometry.core.designer.face import Face, SurfaceType self._grpc_client.log.debug("Requesting edge faces from server.") - grpc_faces = self._edges_stub.GetFaces(self._grpc_id).faces + response = self._grpc_client.services.edges.get_faces(id=self._id) return [ Face( - grpc_face.id, - SurfaceType(grpc_face.surface_type), + face_resp.get("id"), + SurfaceType(face_resp.get("surface_type")), self._body, self._grpc_client, - grpc_face.is_reversed, + face_resp.get("is_reversed"), ) - for grpc_face in grpc_faces + for face_resp in response.get("faces") ] @property - @protect_grpc @ensure_design_is_active def start(self) -> Point3D: """Start point of the edge.""" @@ -197,14 +183,11 @@ def start(self) -> Point3D: except GeometryRuntimeError: # pragma: no cover # Only for versions earlier than 24.2.0 (before the introduction of the shape property) self._grpc_client.log.debug("Requesting edge start point from server.") - response = self._edges_stub.GetStartAndEndPoints(self._grpc_id) - return Point3D( - [response.start.x, response.start.y, response.start.z], - unit=DEFAULT_UNITS.SERVER_LENGTH, + return self._grpc_client.services.edges.get_start_and_end_points(id=self._id).get( + "start" ) @property - @protect_grpc @ensure_design_is_active def end(self) -> Point3D: """End point of the edge.""" @@ -213,22 +196,16 @@ def end(self) -> Point3D: except GeometryRuntimeError: # pragma: no cover # Only for versions earlier than 24.2.0 (before the introduction of the shape property) self._grpc_client.log.debug("Requesting edge end point from server.") - response = self._edges_stub.GetStartAndEndPoints(self._grpc_id) - return Point3D( - [response.end.x, response.end.y, response.end.z], unit=DEFAULT_UNITS.SERVER_LENGTH - ) + return self._grpc_client.services.edges.get_start_and_end_points(id=self._id).get("end") @property - @protect_grpc @ensure_design_is_active @min_backend_version(25, 2, 0) def bounding_box(self) -> BoundingBox: """Bounding box of the edge.""" self._grpc_client.log.debug("Requesting bounding box from server.") - result = self._edges_stub.GetBoundingBox(self._grpc_id) - - min_corner = grpc_point_to_point3d(result.min) - max_corner = grpc_point_to_point3d(result.max) - center = grpc_point_to_point3d(result.center) - return BoundingBox(min_corner, max_corner, center) + response = self._grpc_client.services.edges.get_bounding_box(id=self._id) + return BoundingBox( + response.get("min_corner"), response.get("max_corner"), response.get("center") + ) diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index f563766d3d..143569beb9 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -31,7 +31,6 @@ from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier from ansys.api.geometry.v0.commands_pb2 import FaceOffsetRequest from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub -from ansys.api.geometry.v0.edges_pb2_grpc import EdgesStub from ansys.api.geometry.v0.faces_pb2 import ( CreateIsoParamCurvesRequest, EvaluateRequest, @@ -39,7 +38,6 @@ SetColorRequest, ) from ansys.api.geometry.v0.faces_pb2_grpc import FacesStub -from ansys.api.geometry.v0.models_pb2 import Edge as GRPCEdge from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.connection.conversions import ( grpc_curve_to_curve, @@ -193,7 +191,6 @@ def __init__( self._body = body self._grpc_client = grpc_client self._faces_stub = FacesStub(grpc_client.channel) - self._edges_stub = EdgesStub(grpc_client.channel) self._commands_stub = CommandsStub(grpc_client.channel) self._is_reversed = is_reversed self._shape = None @@ -263,9 +260,20 @@ def area(self) -> Quantity: @ensure_design_is_active def edges(self) -> list[Edge]: """List of all edges of the face.""" + from ansys.geometry.core.designer.edge import CurveType + self._grpc_client.log.debug("Requesting face edges from server.") edges_response = self._faces_stub.GetEdges(self._grpc_id) - return self.__grpc_edges_to_edges(edges_response.edges) + return [ + Edge( + edge.id, + CurveType(edge.curve_type), + self._body, + self._grpc_client, + edge.is_reversed, + ) + for edge in edges_response.edges + ] @property @protect_grpc @@ -294,10 +302,7 @@ def loops(self) -> list[FaceLoop]: ], DEFAULT_UNITS.SERVER_LENGTH, ) - grpc_edges = [ - self._edges_stub.Get(EntityIdentifier(id=edge_id)) for edge_id in grpc_loop.edges - ] - edges = self.__grpc_edges_to_edges(grpc_edges) + edges = self.__grpc_edge_ids_to_edges(grpc_loop.edges) loops.append( FaceLoop(type=type, length=length, min_bbox=min, max_bbox=max, edges=edges) ) @@ -446,30 +451,31 @@ def point(self, u: float = 0.5, v: float = 0.5) -> Point3D: response = self._faces_stub.Evaluate(EvaluateRequest(id=self.id, u=u, v=v)).point return Point3D([response.x, response.y, response.z], DEFAULT_UNITS.SERVER_LENGTH) - def __grpc_edges_to_edges(self, edges_grpc: list[GRPCEdge]) -> list[Edge]: - """Transform a list of gRPC edge messages into actual ``Edge`` objects. + def __grpc_edge_ids_to_edges(self, edge_ids: list[str]) -> list[Edge]: + """Retrieve from a list of gRPC ids the actual ``Edge`` objects. Parameters ---------- - edges_grpc : list[GRPCEdge] - list of gRPC messages of type ``Edge``. + edge_ids : list[dict] + List of edges. Returns ------- list[Edge] ``Edge`` objects to obtain from gRPC messages. """ - from ansys.geometry.core.designer.edge import CurveType, Edge + from ansys.geometry.core.designer.edge import CurveType edges = [] - for edge_grpc in edges_grpc: + for edge_id in edge_ids: + response = self._grpc_client.services.edges.get_edge(id=edge_id) edges.append( Edge( - edge_grpc.id, - CurveType(edge_grpc.curve_type), + response.get("edge_id"), + CurveType(response.get("edge_curve_type")), self._body, self._grpc_client, - edge_grpc.is_reversed, + response.get("edge_is_reversed"), ) ) return edges From ac28a1088ad4e7dcad23b3486e5fd26d48c6b58b Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Sun, 18 May 2025 12:55:34 +0200 Subject: [PATCH 08/20] fix: remove EdgesStub from edge class --- src/ansys/geometry/core/designer/edge.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index ffb5622ce8..93d8a913a3 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -27,7 +27,6 @@ from pint import Quantity from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier -from ansys.api.geometry.v0.edges_pb2_grpc import EdgesStub from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.errors import GeometryRuntimeError from ansys.geometry.core.math.bbox import BoundingBox @@ -85,7 +84,6 @@ def __init__( self._curve_type = curve_type self._body = body self._grpc_client = grpc_client - self._edges_stub = EdgesStub(grpc_client.channel) self._is_reversed = is_reversed self._shape = None From f988d86841818bf47bb5fd8f5fdf04adc1642fee Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Sun, 18 May 2025 13:37:47 +0200 Subject: [PATCH 09/20] fix: missing imports --- src/ansys/geometry/core/_grpc/_services/v0/conversions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index 498f50aaed..c6cea45c76 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -663,6 +663,8 @@ def from_grpc_curve_to_curve(curve: GRPCCurveGeometry) -> "Curve": Curve Resulting converted curve. """ + from ansys.geometry.core.math.point import Point3D + from ansys.geometry.core.math.vector import UnitVector3D from ansys.geometry.core.shapes.curves.circle import Circle from ansys.geometry.core.shapes.curves.ellipse import Ellipse from ansys.geometry.core.shapes.curves.line import Line From 582a8c3b3a9c64033bd6043f72f281f8923d8cbf Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Sun, 18 May 2025 19:38:02 +0200 Subject: [PATCH 10/20] fix: typo --- src/ansys/geometry/core/_grpc/_services/v0/edges.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/edges.py b/src/ansys/geometry/core/_grpc/_services/v0/edges.py index 3ef02576d3..1423d76602 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/edges.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/edges.py @@ -132,9 +132,9 @@ def get_faces(self, **kwargs) -> dict: # noqa: D102 return { "faces": [ { - "id": response.id, - "surface_type": response.surface_type, - "is_reversed": response.is_reversed, + "id": face.id, + "surface_type": face.surface_type, + "is_reversed": face.is_reversed, } for face in response.faces ], From 11348afb0da0f20516e55e284dc21377e601d3b3 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Sun, 18 May 2025 19:41:37 +0200 Subject: [PATCH 11/20] fix: import --- src/ansys/geometry/core/_grpc/_services/v0/edges.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/edges.py b/src/ansys/geometry/core/_grpc/_services/v0/edges.py index 1423d76602..5c9ed3dc22 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/edges.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/edges.py @@ -27,7 +27,7 @@ from ..base.conversions import to_distance from ..base.edges import GRPCEdgesService -from .conversions import build_grpc_id, from_grpc_point_to_point3d +from .conversions import build_grpc_id, from_grpc_curve_to_curve, from_grpc_point_to_point3d class GRPCEdgesServiceV0(GRPCEdgesService): @@ -66,8 +66,6 @@ def get_edge(self, **kwargs) -> dict: # noqa: D102 @protect_grpc def get_curve(self, **kwargs) -> dict: # noqa: D102 - from .conversions import from_grpc_curve_to_curve - # Create the request - assumes all inputs are valid and of the proper type request = build_grpc_id(kwargs["id"]) From 23f9a6ffe93a2c74a8df6aef983ec96df8006a29 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Sun, 18 May 2025 19:45:13 +0200 Subject: [PATCH 12/20] fix: relocate imports --- .../core/_grpc/_services/v0/coordinate_systems.py | 3 +-- .../core/_grpc/_services/v0/driving_dimensions.py | 12 +++++------- .../geometry/core/_grpc/_services/v0/materials.py | 3 +-- .../core/_grpc/_services/v0/measurement_tools.py | 3 +-- src/ansys/geometry/core/_grpc/_services/v0/parts.py | 3 +-- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/coordinate_systems.py b/src/ansys/geometry/core/_grpc/_services/v0/coordinate_systems.py index 4f75f200c4..a22ba369dd 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/coordinate_systems.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/coordinate_systems.py @@ -26,6 +26,7 @@ from ansys.geometry.core.errors import protect_grpc from ..base.coordinate_systems import GRPCCoordinateSystemService +from .conversions import from_frame_to_grpc_frame, from_grpc_frame_to_frame class GRPCCoordinateSystemServiceV0(GRPCCoordinateSystemService): @@ -51,8 +52,6 @@ def __init__(self, channel: grpc.Channel): # noqa: D102 def create(self, **kwargs) -> dict: # noqa: D102 from ansys.api.geometry.v0.coordinatesystems_pb2 import CreateRequest - from .conversions import from_frame_to_grpc_frame, from_grpc_frame_to_frame - # Create the request - assumes all inputs are valid and of the proper type request = CreateRequest( parent=kwargs["parent_id"], diff --git a/src/ansys/geometry/core/_grpc/_services/v0/driving_dimensions.py b/src/ansys/geometry/core/_grpc/_services/v0/driving_dimensions.py index fad5e92891..e423633ba7 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/driving_dimensions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/driving_dimensions.py @@ -26,6 +26,11 @@ from ansys.geometry.core.errors import protect_grpc from ..base.driving_dimensions import GRPCDrivingDimensionsService +from .conversions import ( + from_driving_dimension_to_grpc_driving_dimension, + from_grpc_driving_dimension_to_driving_dimension, + from_grpc_update_status_to_parameter_update_status, +) class GRPCDrivingDimensionsServiceV0(GRPCDrivingDimensionsService): @@ -51,8 +56,6 @@ def __init__(self, channel: grpc.Channel): # noqa: D102 def get_all_parameters(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.drivingdimensions_pb2 import GetAllRequest - from .conversions import from_grpc_driving_dimension_to_driving_dimension - # Call the gRPC service response = self.stub.GetAll(GetAllRequest()) @@ -68,11 +71,6 @@ def get_all_parameters(self, **kwargs) -> dict: # noqa: D102 def set_parameter(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.drivingdimensions_pb2 import UpdateRequest - from .conversions import ( - from_driving_dimension_to_grpc_driving_dimension, - from_grpc_update_status_to_parameter_update_status, - ) - # Create the request - assumes all inputs are valid and of the proper type request = UpdateRequest( driving_dimension=from_driving_dimension_to_grpc_driving_dimension( diff --git a/src/ansys/geometry/core/_grpc/_services/v0/materials.py b/src/ansys/geometry/core/_grpc/_services/v0/materials.py index 631bdb6251..df9da9b717 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/materials.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/materials.py @@ -26,6 +26,7 @@ from ansys.geometry.core.errors import protect_grpc from ..base.materials import GRPCMaterialsService +from .conversions import from_material_to_grpc_material class GRPCMaterialsServiceV0(GRPCMaterialsService): @@ -51,8 +52,6 @@ def __init__(self, channel: grpc.Channel): # noqa: D102 def add_material(self, **kwargs) -> dict: # noqa: D102 from ansys.api.geometry.v0.materials_pb2 import AddToDocumentRequest - from .conversions import from_material_to_grpc_material - # Create the request - assumes all inputs are valid and of the proper type request = AddToDocumentRequest( material=from_material_to_grpc_material(kwargs["material"]), diff --git a/src/ansys/geometry/core/_grpc/_services/v0/measurement_tools.py b/src/ansys/geometry/core/_grpc/_services/v0/measurement_tools.py index 99f4df853f..36267f429c 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/measurement_tools.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/measurement_tools.py @@ -25,6 +25,7 @@ from ansys.geometry.core.errors import protect_grpc +from ..base.conversions import to_distance from ..base.measurement_tools import GRPCMeasurementToolsService @@ -51,8 +52,6 @@ def __init__(self, channel: grpc.Channel): # noqa: D102 def min_distance_between_objects(self, **kwargs) -> dict: # noqa: D102 from ansys.api.geometry.v0.measuretools_pb2 import MinDistanceBetweenObjectsRequest - from ..base.conversions import to_distance - # Create the request - assumes all inputs are valid and of the proper type # Request is different based on backend_version (25.2 vs. earlier) if kwargs["backend_version"] < (25, 2, 0): diff --git a/src/ansys/geometry/core/_grpc/_services/v0/parts.py b/src/ansys/geometry/core/_grpc/_services/v0/parts.py index a319f341d0..ec344febea 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/parts.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/parts.py @@ -26,6 +26,7 @@ from ansys.geometry.core.errors import protect_grpc from ..base.parts import GRPCPartsService +from .conversions import from_design_file_format_to_grpc_part_export_format class GRPCPartsServiceV0(GRPCPartsService): @@ -51,8 +52,6 @@ def __init__(self, channel: grpc.Channel): # noqa: D102 def export(self, **kwargs) -> dict: # noqa: D102 from ansys.api.geometry.v0.parts_pb2 import ExportRequest - from .conversions import from_design_file_format_to_grpc_part_export_format - # Create the request - assumes all inputs are valid and of the proper type request = ExportRequest( format=from_design_file_format_to_grpc_part_export_format(kwargs["format"]) From d0bd3972c951da969b86de55f119d80e54efca41 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Sun, 18 May 2025 20:07:49 +0200 Subject: [PATCH 13/20] fix: response arg in get_curve --- src/ansys/geometry/core/_grpc/_services/v0/edges.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/edges.py b/src/ansys/geometry/core/_grpc/_services/v0/edges.py index 5c9ed3dc22..24c8cef625 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/edges.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/edges.py @@ -74,7 +74,7 @@ def get_curve(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary return { - "curve": from_grpc_curve_to_curve(response.curve), + "curve": from_grpc_curve_to_curve(response), } @protect_grpc From 23686417ed83de5acb945e1322758c11c91e8a04 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Wed, 21 May 2025 18:58:47 +0200 Subject: [PATCH 14/20] feat: adding faces support in grpc --- .pre-commit-config.yaml | 2 +- .../geometry/core/_grpc/_services/_service.py | 28 ++ .../core/_grpc/_services/base/conversions.py | 23 ++ .../core/_grpc/_services/base/faces.py | 95 +++++++ .../core/_grpc/_services/v0/conversions.py | 40 +++ .../geometry/core/_grpc/_services/v0/faces.py | 255 ++++++++++++++++++ .../geometry/core/_grpc/_services/v1/faces.py | 92 +++++++ src/ansys/geometry/core/designer/face.py | 178 +++++------- 8 files changed, 594 insertions(+), 119 deletions(-) create mode 100644 src/ansys/geometry/core/_grpc/_services/base/faces.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v0/faces.py create mode 100644 src/ansys/geometry/core/_grpc/_services/v1/faces.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5b709dd582..b622344d12 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.11.10 hooks: - - id: ruff + - id: ruff-check - id: ruff-format - repo: https://github.com/codespell-project/codespell diff --git a/src/ansys/geometry/core/_grpc/_services/_service.py b/src/ansys/geometry/core/_grpc/_services/_service.py index 9644653c17..a69177fb36 100644 --- a/src/ansys/geometry/core/_grpc/_services/_service.py +++ b/src/ansys/geometry/core/_grpc/_services/_service.py @@ -30,6 +30,7 @@ from .base.designs import GRPCDesignsService from .base.driving_dimensions import GRPCDrivingDimensionsService from .base.edges import GRPCEdgesService +from .base.faces import GRPCFacesService from .base.materials import GRPCMaterialsService from .base.measurement_tools import GRPCMeasurementToolsService from .base.named_selection import GRPCNamedSelectionService @@ -83,6 +84,7 @@ def __init__(self, channel: grpc.Channel, version: GeometryApiProtos | str | Non self._designs = None self._driving_dimensions = None self._edges = None + self._faces = None self._materials = None self._measurement_tools = None self._named_selection = None @@ -272,6 +274,32 @@ def edges(self) -> GRPCEdgesService: return self._edges + @property + def faces(self) -> GRPCFacesService: + """ + Get the faces service for the specified version. + + Returns + ------- + GRPCEdgesService + The faces service for the specified version. + """ + if not self._faces: + # Import the appropriate faces service based on the version + from .v0.faces import GRPCFacesServiceV0 + from .v1.faces import GRPCFacesServiceV1 + + if self.version == GeometryApiProtos.V0: + self._faces = GRPCFacesServiceV0(self.channel) + elif self.version == GeometryApiProtos.V1: # pragma: no cover + # V1 is not implemented yet + self._faces = GRPCFacesServiceV1(self.channel) + else: # pragma: no cover + # This should never happen as the version is set in the constructor + raise ValueError(f"Unsupported version: {self.version}") + + return self._faces + @property def materials(self) -> GRPCMaterialsService: """ diff --git a/src/ansys/geometry/core/_grpc/_services/base/conversions.py b/src/ansys/geometry/core/_grpc/_services/base/conversions.py index eb4d6ad6fd..81892a0a00 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/base/conversions.py @@ -21,6 +21,8 @@ # SOFTWARE. """Module containing server-version agnostic conversions.""" +from pint import Quantity + from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Distance, Measurement @@ -75,3 +77,24 @@ def to_distance(value: float | int) -> Distance: The value should represent a length in the server's unit system. """ return Distance(value, DEFAULT_UNITS.SERVER_LENGTH) + + +def to_area(value: float | int) -> Quantity: + """Convert a server value to an area object. + + Parameters + ---------- + value : float | int + Value to convert. + + Returns + ------- + Quantity + Converted area. + + Notes + ----- + The value is converted to a Quantity object using the default server area unit. + The value should represent an area in the server's unit system. + """ + return Quantity(value, DEFAULT_UNITS.SERVER_AREA) diff --git a/src/ansys/geometry/core/_grpc/_services/base/faces.py b/src/ansys/geometry/core/_grpc/_services/base/faces.py new file mode 100644 index 0000000000..e310c5018b --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/base/faces.py @@ -0,0 +1,95 @@ +# 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 containing the faces service implementation (abstraction layer).""" + +from abc import ABC, abstractmethod + +import grpc + + +class GRPCFacesService(ABC): # pragma: no cover + """Faces service for gRPC communication with the Geometry server. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + def __init__(self, channel: grpc.Channel): + """Initialize the GRPCFacesService class.""" + pass + + @abstractmethod + def get_surface(self, **kwargs) -> dict: + """Get the surface of a face.""" + pass + + @abstractmethod + def get_box_uv(self, **kwargs) -> dict: + """Get the UV box of a face.""" + pass + + @abstractmethod + def get_area(self, **kwargs) -> dict: + """Get the area of a face.""" + pass + + @abstractmethod + def get_edges(self, **kwargs) -> dict: + """Get the edges of a face.""" + pass + + @abstractmethod + def get_loops(self, **kwargs) -> dict: + """Get the loops of a face.""" + pass + + @abstractmethod + def get_color(self, **kwargs) -> dict: + """Get the color of a face.""" + pass + + @abstractmethod + def get_bounding_box(self, **kwargs) -> dict: + """Get the bounding box of a face.""" + pass + + @abstractmethod + def set_color(self, **kwargs) -> dict: + """Set the color of a face.""" + pass + + @abstractmethod + def get_normal(self, **kwargs) -> dict: + """Get the normal of a face.""" + pass + + @abstractmethod + def evaluate(self, **kwargs) -> dict: + """Evaluate a face at a given parameter.""" + pass + + @abstractmethod + def create_iso_parametric_curve(self, **kwargs) -> dict: + """Create an iso-parametric curve on a face.""" + pass diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index c6cea45c76..ed19c9fbdf 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -60,6 +60,7 @@ from ansys.geometry.core.connection.backend import BackendType from ansys.geometry.core.designer.design import DesignFileFormat + from ansys.geometry.core.designer.face import SurfaceType from ansys.geometry.core.materials.material import Material from ansys.geometry.core.materials.property import MaterialProperty from ansys.geometry.core.math.frame import Frame @@ -786,6 +787,45 @@ def from_surface_to_grpc_surface(surface: "Surface") -> tuple[GRPCSurface, GRPCS return grpc_surface, surface_type +def from_grpc_surface_to_surface(surface: GRPCSurface, surface_type: "SurfaceType") -> "Surface": + """Convert a surface gRPC message to a ``Surface`` class. + + Parameters + ---------- + surface : GRPCSurface + Geometry service gRPC surface message. + + Returns + ------- + Surface + Resulting converted surface. + """ + from ansys.geometry.core.designer.face import SurfaceType + from ansys.geometry.core.shapes.surfaces.cone import Cone + from ansys.geometry.core.shapes.surfaces.cylinder import Cylinder + from ansys.geometry.core.shapes.surfaces.plane import PlaneSurface + from ansys.geometry.core.shapes.surfaces.sphere import Sphere + from ansys.geometry.core.shapes.surfaces.torus import Torus + + origin = from_grpc_point_to_point3d(surface.origin) + axis = UnitVector3D([surface.axis.x, surface.axis.y, surface.axis.z]) + reference = UnitVector3D([surface.reference.x, surface.reference.y, surface.reference.z]) + + if surface_type == SurfaceType.SURFACETYPE_CONE: + result = Cone(origin, surface.radius, surface.half_angle, reference, axis) + elif surface_type == SurfaceType.SURFACETYPE_CYLINDER: + result = Cylinder(origin, surface.radius, reference, axis) + elif surface_type == SurfaceType.SURFACETYPE_SPHERE: + result = Sphere(origin, surface.radius, reference, axis) + elif surface_type == SurfaceType.SURFACETYPE_TORUS: + result = Torus(origin, surface.major_radius, surface.minor_radius, reference, axis) + elif surface_type == SurfaceType.SURFACETYPE_PLANE: + result = PlaneSurface(origin, reference, axis) + else: + result = None + return result + + def from_grpc_backend_type_to_backend_type( grpc_backend_type: GRPCBackendType, ) -> "BackendType": diff --git a/src/ansys/geometry/core/_grpc/_services/v0/faces.py b/src/ansys/geometry/core/_grpc/_services/v0/faces.py new file mode 100644 index 0000000000..50a1ec0a37 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v0/faces.py @@ -0,0 +1,255 @@ +# 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 containing the faces service implementation for v0.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.conversions import to_area, to_distance +from ..base.faces import GRPCFacesService +from .conversions import ( + build_grpc_id, + from_grpc_curve_to_curve, + from_grpc_point_to_point3d, + from_grpc_surface_to_surface, +) + + +class GRPCFacesServiceV0(GRPCFacesService): # pragma: no cover + """Faces service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + faces service. It is specifically designed for the v0 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v0.faces_pb2_grpc import FacesStub + + self.stub = FacesStub(channel) + + @protect_grpc + def get_surface(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetSurface(request=request) + + # Return the response - formatted as a dictionary + return { + "surface": from_grpc_surface_to_surface(response, kwargs["surface_type"]), + } + + @protect_grpc + def get_box_uv(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetBoxUV(request=request) + + # Return the response - formatted as a dictionary + return { + "uv_box": { + "u": (response.start_u, response.end_u), + "v": (response.start_v, response.end_v), + } + } + + @protect_grpc + def get_area(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetArea(request=request) + + # Return the response - formatted as a dictionary + return {"area": to_area(response.area)} + + @protect_grpc + def get_edges(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetEdges(request=request) + + # Return the response - formatted as a dictionary + return { + "edges": [ + { + "id": edge.id, + "curve_type": edge.curve_type, + "is_reversed": edge.is_reversed, + } + for edge in response.edges + ] + } + + @protect_grpc + def get_loops(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetLoops(request=request) + + # Return the response - formatted as a dictionary + return { + "loops": [ + { + "type": loop.type, + "length": to_distance(loop.length).value, + "min_corner": from_grpc_point_to_point3d(loop.bounding_box.min), + "max_corner": from_grpc_point_to_point3d(loop.bounding_box.max), + "edges": [ + { + "id": edge.id, + "curve_type": edge.curve_type, + "is_reversed": edge.is_reversed, + } + for edge in loop.edges + ], + } + for loop in response.loops + ] + } + + @protect_grpc + def get_color(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetColor(request=request) + + # Return the response - formatted as a dictionary + return {"color": response.color} + + @protect_grpc + def get_bounding_box(self, **kwargs) -> dict: # noqa: D102 + # Create the request - assumes all inputs are valid and of the proper type + request = build_grpc_id(kwargs["id"]) + + # Call the gRPC service + response = self.stub.GetBoundingBox(request=request) + + # Return the response - formatted as a dictionary + return { + "min_corner": from_grpc_point_to_point3d(response.min), + "max_corner": from_grpc_point_to_point3d(response.max), + "center": from_grpc_point_to_point3d(response.center), + } + + @protect_grpc + def set_color(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.faces_pb2 import SetColorRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = SetColorRequest( + face_id=kwargs["id"], + color=kwargs["color"], + ) + # Call the gRPC service + response = self.stub.SetColor(request=request) + + # Return the response - formatted as a dictionary + return {"success": response.success} + + @protect_grpc + def get_normal(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.faces_pb2 import GetNormalRequest + from ansys.geometry.core.math.vector import UnitVector3D + + # Create the request - assumes all inputs are valid and of the proper type + request = GetNormalRequest( + id=kwargs["id"], + u=kwargs["u"], + v=kwargs["v"], + ) + + # Call the gRPC service + response = self.stub.GetNormal(request=request) + + # Return the response - formatted as a dictionary + return { + "normal": UnitVector3D( + [response.direction.x, response.direction.y, response.direction.z] + ), + } + + @protect_grpc + def evaluate(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.faces_pb2 import EvaluateRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = EvaluateRequest( + id=kwargs["id"], + u=kwargs["u"], + v=kwargs["v"], + ) + + # Call the gRPC service + response = self.stub.Evaluate(request=request) + + # Return the response - formatted as a dictionary + return { + "point": from_grpc_point_to_point3d(response.point), + } + + @protect_grpc + def create_iso_parametric_curve(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.faces_pb2 import CreateIsoParamCurvesRequest + from ansys.geometry.core.shapes.parameterization import Interval + + # Create the request - assumes all inputs are valid and of the proper type + request = CreateIsoParamCurvesRequest( + id=kwargs["id"], + u_dir_curve=kwargs["use_u_param"], + proportion=kwargs["parameter"], + ) + + # Call the gRPC service + response = self.stub.CreateIsoParamCurves(request=request) + + # Return the response - formatted as a dictionary + return { + "curves": [ + { + "geometry": from_grpc_curve_to_curve(curve.curve), + "start": from_grpc_point_to_point3d(curve.start), + "end": from_grpc_point_to_point3d(curve.end), + "interval": Interval(curve.interval_start, curve.interval_end), + "length": to_distance(curve.length).value, + } + for curve in response.curves + ] + } diff --git a/src/ansys/geometry/core/_grpc/_services/v1/faces.py b/src/ansys/geometry/core/_grpc/_services/v1/faces.py new file mode 100644 index 0000000000..aa6efbe648 --- /dev/null +++ b/src/ansys/geometry/core/_grpc/_services/v1/faces.py @@ -0,0 +1,92 @@ +# 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 containing the faces service implementation for v1.""" + +import grpc + +from ansys.geometry.core.errors import protect_grpc + +from ..base.faces import GRPCFacesService + + +class GRPCFacesServiceV1(GRPCFacesService): # pragma: no cover + """Faces service for gRPC communication with the Geometry server. + + This class provides methods to interact with the Geometry server's + faces service. It is specifically designed for the v1 version of the + Geometry API. + + Parameters + ---------- + channel : grpc.Channel + The gRPC channel to the server. + """ + + @protect_grpc + def __init__(self, channel: grpc.Channel): # noqa: D102 + from ansys.api.geometry.v1.faces_pb2_grpc import FacesStub + + self.stub = FacesStub(channel) + + @protect_grpc + def get_surface(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_box_uv(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_area(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_edges(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_loops(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_color(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_bounding_box(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def set_color(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def get_normal(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def evaluate(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError + + @protect_grpc + def create_iso_parametric_curve(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index 143569beb9..a038050b73 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -31,19 +31,7 @@ from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier from ansys.api.geometry.v0.commands_pb2 import FaceOffsetRequest from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub -from ansys.api.geometry.v0.faces_pb2 import ( - CreateIsoParamCurvesRequest, - EvaluateRequest, - GetNormalRequest, - SetColorRequest, -) -from ansys.api.geometry.v0.faces_pb2_grpc import FacesStub from ansys.geometry.core.connection.client import GrpcClient -from ansys.geometry.core.connection.conversions import ( - grpc_curve_to_curve, - grpc_point_to_point3d, - grpc_surface_to_surface, -) from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.errors import GeometryRuntimeError, protect_grpc from ansys.geometry.core.math.bbox import BoundingBox @@ -59,7 +47,6 @@ graphics_required, min_backend_version, ) -from ansys.geometry.core.misc.measurements import DEFAULT_UNITS from ansys.geometry.core.misc.options import TessellationOptions from ansys.geometry.core.shapes.box_uv import BoxUV from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve @@ -190,7 +177,6 @@ def __init__( self._surface_type = surface_type self._body = body self._grpc_client = grpc_client - self._faces_stub = FacesStub(grpc_client.channel) self._commands_stub = CommandsStub(grpc_client.channel) self._is_reversed = is_reversed self._shape = None @@ -217,7 +203,6 @@ def body(self) -> "Body": return self._body @property - @protect_grpc @ensure_design_is_active @min_backend_version(24, 2, 0) def shape(self) -> TrimmedSurface: @@ -229,10 +214,12 @@ def shape(self) -> TrimmedSurface: if self._shape is None: self._grpc_client.log.debug("Requesting face properties from server.") - surface_response = self._faces_stub.GetSurface(self._grpc_id) - geometry = grpc_surface_to_surface(surface_response, self._surface_type) - box = self._faces_stub.GetBoxUV(self._grpc_id) - box_uv = BoxUV(Interval(box.start_u, box.end_u), Interval(box.start_v, box.end_v)) + geometry = self._grpc_client.services.faces.get_surface( + id=self.id, surface_type=self.surface_type + ).get("surface") + response = self._grpc_client.services.faces.get_box_uv(id=self.id).get("uv_box") + u_comp, v_comp = response.get("u"), response.get("v") + box_uv = BoxUV(Interval(*u_comp), Interval(*v_comp)) self._shape = ( ReversedTrimmedSurface(geometry, box_uv) @@ -247,62 +234,55 @@ def surface_type(self) -> SurfaceType: return self._surface_type @property - @protect_grpc @ensure_design_is_active def area(self) -> Quantity: """Calculated area of the face.""" self._grpc_client.log.debug("Requesting face area from server.") - area_response = self._faces_stub.GetArea(self._grpc_id) - return Quantity(area_response.area, DEFAULT_UNITS.SERVER_AREA) + return self._grpc_client.services.faces.get_area(id=self.id).get("area") @property - @protect_grpc @ensure_design_is_active def edges(self) -> list[Edge]: """List of all edges of the face.""" from ansys.geometry.core.designer.edge import CurveType self._grpc_client.log.debug("Requesting face edges from server.") - edges_response = self._faces_stub.GetEdges(self._grpc_id) + response = self._grpc_client.services.faces.get_edges(id=self.id) return [ Edge( - edge.id, - CurveType(edge.curve_type), + edge.get("id"), + CurveType(edge.get("curve_type")), self._body, self._grpc_client, - edge.is_reversed, + edge.get("is_reversed"), ) - for edge in edges_response.edges + for edge in response.get("edges") ] @property - @protect_grpc @ensure_design_is_active def loops(self) -> list[FaceLoop]: """List of all loops of the face.""" + from ansys.geometry.core.designer.edge import CurveType + self._grpc_client.log.debug("Requesting face loops from server.") - grpc_loops = self._faces_stub.GetLoops(EntityIdentifier(id=self.id)).loops + response_loops = self._grpc_client.services.faces.get_edges(id=self.id).get("loops") loops = [] - for grpc_loop in grpc_loops: - type = FaceLoopType(grpc_loop.type) - length = Quantity(grpc_loop.length, DEFAULT_UNITS.SERVER_LENGTH) - min = Point3D( - [ - grpc_loop.bounding_box.min.x, - grpc_loop.bounding_box.min.y, - grpc_loop.bounding_box.min.z, - ], - DEFAULT_UNITS.SERVER_LENGTH, - ) - max = Point3D( - [ - grpc_loop.bounding_box.max.x, - grpc_loop.bounding_box.max.y, - grpc_loop.bounding_box.max.z, - ], - DEFAULT_UNITS.SERVER_LENGTH, - ) - edges = self.__grpc_edge_ids_to_edges(grpc_loop.edges) + for response_loop in response_loops: + type = FaceLoopType(response_loop.get("type")) + length = response_loop.get("length") + min = response_loop.get("min_corner") + max = response_loop.get("max_corner") + edges = [ + Edge( + edge.get("id"), + CurveType(edge.get("curve_type")), + self._body, + self._grpc_client, + edge.get("is_reversed"), + ) + for edge in response_loop.get("edges") + ] loops.append( FaceLoop(type=type, length=length, min_bbox=min, max_bbox=max, edges=edges) ) @@ -310,7 +290,6 @@ def loops(self) -> list[FaceLoop]: return loops @property - @protect_grpc @min_backend_version(25, 2, 0) def color(self) -> str: """Get the current color of the face.""" @@ -319,11 +298,11 @@ def color(self) -> str: self._color = DEFAULT_COLOR # If color is not cached, retrieve from the server - response = self._faces_stub.GetColor(EntityIdentifier(id=self.id)) + response = self._grpc_client.services.faces.get_color(id=self.id) # Return if valid color returned - if response.color: - self._color = mcolors.to_hex(response.color, keep_alpha=True) + if response.get("color"): + self._color = mcolors.to_hex(response.get("color"), keep_alpha=True) else: self._color = DEFAULT_COLOR @@ -344,34 +323,26 @@ def opacity(self, opacity: float) -> None: self.set_opacity(opacity) @property - @protect_grpc @min_backend_version(25, 2, 0) def bounding_box(self) -> BoundingBox: """Get the bounding box for the face.""" self._grpc_client.log.debug(f"Getting bounding box for {self.id}.") + response = self._grpc_client.services.faces.get_bounding_box(id=self.id) + return BoundingBox( + response.get("min_corner"), response.get("max_corner"), response.get("center") + ) - result = self._faces_stub.GetBoundingBox(request=self._grpc_id) - min_point = grpc_point_to_point3d(result.min) - max_point = grpc_point_to_point3d(result.max) - center = grpc_point_to_point3d(result.center) - - return BoundingBox(min_point, max_point, center) - - @protect_grpc @check_input_types @min_backend_version(25, 2, 0) def set_color(self, color: str | tuple[float, float, float]) -> None: """Set the color of the face.""" self._grpc_client.log.debug(f"Setting face color of {self.id} to {color}.") color = convert_color_to_hex(color) - - self._faces_stub.SetColor( - SetColorRequest( - face_id=self.id, - color=color, - ) - ) - self._color = color + response = self._grpc_client.services.faces.set_color(id=self.id, color=color) + if response.get("success"): + self._color = color + else: # pragma: no cover + raise GeometryRuntimeError(f"Failed to set color {color} for face {self.id}. ") @check_input_types @min_backend_version(25, 2, 0) @@ -383,7 +354,6 @@ def set_opacity(self, opacity: float) -> None: new_color = self._color[0:7] + opacity self.set_color(new_color) - @protect_grpc @ensure_design_is_active def normal(self, u: float = 0.5, v: float = 0.5) -> UnitVector3D: """Get the normal direction to the face at certain UV coordinates. @@ -415,10 +385,8 @@ def normal(self, u: float = 0.5, v: float = 0.5) -> UnitVector3D: except GeometryRuntimeError: # pragma: no cover # Only for versions earlier than 24.2.0 (before the introduction of the shape property) self._grpc_client.log.debug(f"Requesting face normal from server with (u,v)=({u},{v}).") - response = self._faces_stub.GetNormal(GetNormalRequest(id=self.id, u=u, v=v)).direction - return UnitVector3D([response.x, response.y, response.z]) + return self._grpc_client.services.faces.get_normal(id=self.id, u=u, v=v).get("normal") - @protect_grpc @ensure_design_is_active def point(self, u: float = 0.5, v: float = 0.5) -> Point3D: """Get a point of the face evaluated at certain UV coordinates. @@ -448,39 +416,8 @@ def point(self, u: float = 0.5, v: float = 0.5) -> Point3D: except GeometryRuntimeError: # pragma: no cover # Only for versions earlier than 24.2.0 (before the introduction of the shape property) self._grpc_client.log.debug(f"Requesting face point from server with (u,v)=({u},{v}).") - response = self._faces_stub.Evaluate(EvaluateRequest(id=self.id, u=u, v=v)).point - return Point3D([response.x, response.y, response.z], DEFAULT_UNITS.SERVER_LENGTH) - - def __grpc_edge_ids_to_edges(self, edge_ids: list[str]) -> list[Edge]: - """Retrieve from a list of gRPC ids the actual ``Edge`` objects. - - Parameters - ---------- - edge_ids : list[dict] - List of edges. + return self._grpc_client.services.faces.evaluate(id=self.id, u=u, v=v).get("point") - Returns - ------- - list[Edge] - ``Edge`` objects to obtain from gRPC messages. - """ - from ansys.geometry.core.designer.edge import CurveType - - edges = [] - for edge_id in edge_ids: - response = self._grpc_client.services.edges.get_edge(id=edge_id) - edges.append( - Edge( - response.get("edge_id"), - CurveType(response.get("edge_curve_type")), - self._body, - self._grpc_client, - response.get("edge_is_reversed"), - ) - ) - return edges - - @protect_grpc @ensure_design_is_active def create_isoparametric_curves( self, use_u_param: bool, parameter: float @@ -503,20 +440,25 @@ def create_isoparametric_curves( list[TrimmedCurve] list of curves that were created. """ - curves = self._faces_stub.CreateIsoParamCurves( - CreateIsoParamCurvesRequest(id=self.id, u_dir_curve=use_u_param, proportion=parameter) - ).curves + self._grpc_client.log.debug( + f"Creating isoparametric curves on face {self.id} with parameter={parameter}" + f" and use_u_param={use_u_param}." + ) + response = self._grpc_client.services.faces.create_iso_parametric_curve( + id=self.id, use_u_param=use_u_param, parameter=parameter + ) trimmed_curves = [] - for c in curves: - geometry = grpc_curve_to_curve(c.curve) - start = Point3D([c.start.x, c.start.y, c.start.z], unit=DEFAULT_UNITS.SERVER_LENGTH) - end = Point3D([c.end.x, c.end.y, c.end.z], unit=DEFAULT_UNITS.SERVER_LENGTH) - interval = Interval(c.interval_start, c.interval_end) - length = Quantity(c.length, DEFAULT_UNITS.SERVER_LENGTH) - + for curve in response.get("curves"): trimmed_curves.append( - TrimmedCurve(geometry, start, end, interval, length, self._grpc_client) + TrimmedCurve( + curve.get("geometry"), + curve.get("start"), + curve.get("end"), + curve.get("interval"), + curve.get("length"), + self._grpc_client, + ) ) return trimmed_curves From d9aafa8b0de4985accde5dbd664edd212b464af4 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Wed, 21 May 2025 19:09:33 +0200 Subject: [PATCH 15/20] fix: missing import --- src/ansys/geometry/core/_grpc/_services/v0/conversions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index ed19c9fbdf..abb387acc0 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -801,6 +801,7 @@ def from_grpc_surface_to_surface(surface: GRPCSurface, surface_type: "SurfaceTyp Resulting converted surface. """ from ansys.geometry.core.designer.face import SurfaceType + from ansys.geometry.core.math.vector import UnitVector3D from ansys.geometry.core.shapes.surfaces.cone import Cone from ansys.geometry.core.shapes.surfaces.cylinder import Cylinder from ansys.geometry.core.shapes.surfaces.plane import PlaneSurface From 5d903c919a3ad72a2c7e5a19d0fb9943eb748003 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 22 May 2025 08:36:06 +0200 Subject: [PATCH 16/20] fix: method call --- src/ansys/geometry/core/designer/face.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index a038050b73..10f3369350 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -266,7 +266,7 @@ def loops(self) -> list[FaceLoop]: from ansys.geometry.core.designer.edge import CurveType self._grpc_client.log.debug("Requesting face loops from server.") - response_loops = self._grpc_client.services.faces.get_edges(id=self.id).get("loops") + response_loops = self._grpc_client.services.faces.get_loops(id=self.id).get("loops") loops = [] for response_loop in response_loops: type = FaceLoopType(response_loop.get("type")) From 5f3a8a6faca71b512fb9074bca9b5df078cfae90 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 22 May 2025 08:39:32 +0200 Subject: [PATCH 17/20] fix: typo --- src/ansys/geometry/core/_grpc/_services/_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/_grpc/_services/_service.py b/src/ansys/geometry/core/_grpc/_services/_service.py index a69177fb36..fc9235517c 100644 --- a/src/ansys/geometry/core/_grpc/_services/_service.py +++ b/src/ansys/geometry/core/_grpc/_services/_service.py @@ -317,7 +317,7 @@ def materials(self) -> GRPCMaterialsService: if self.version == GeometryApiProtos.V0: self._materials = GRPCMaterialsServiceV0(self.channel) - elif self.version == GeometryApiProtos.V1: + elif self.version == GeometryApiProtos.V1: # pragma: no cover # V1 is not implemented yet self._materials = GRPCMaterialsServiceV1(self.channel) else: # pragma: no cover From f787835e7f511270dcfb5eddbe96e48feb3f6dc7 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 22 May 2025 08:44:08 +0200 Subject: [PATCH 18/20] fix: typo --- src/ansys/geometry/core/_grpc/_services/v0/designs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py index b8edd8d224..8ad2864385 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -147,7 +147,7 @@ def stream_download_export(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary data = bytes() for elem in response: - data += response.data + data += elem.data return {"data": data} From 94edaaeecf27c55103fc9b66e14526e7437f8ff6 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 22 May 2025 09:11:10 +0200 Subject: [PATCH 19/20] fix: failing get_loops --- .../geometry/core/_grpc/_services/v0/faces.py | 9 +-------- src/ansys/geometry/core/designer/face.py | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/faces.py b/src/ansys/geometry/core/_grpc/_services/v0/faces.py index 50a1ec0a37..02e381b8aa 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/faces.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/faces.py @@ -130,14 +130,7 @@ def get_loops(self, **kwargs) -> dict: # noqa: D102 "length": to_distance(loop.length).value, "min_corner": from_grpc_point_to_point3d(loop.bounding_box.min), "max_corner": from_grpc_point_to_point3d(loop.bounding_box.max), - "edges": [ - { - "id": edge.id, - "curve_type": edge.curve_type, - "is_reversed": edge.is_reversed, - } - for edge in loop.edges - ], + "edges": [edge for edge in loop.edges], } for loop in response.loops ] diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index 10f3369350..66bbbe80e2 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -273,16 +273,18 @@ def loops(self) -> list[FaceLoop]: length = response_loop.get("length") min = response_loop.get("min_corner") max = response_loop.get("max_corner") - edges = [ - Edge( - edge.get("id"), - CurveType(edge.get("curve_type")), - self._body, - self._grpc_client, - edge.get("is_reversed"), + edges = [] + for edge_id in response_loop.get("edges"): + response_edge = self._grpc_client.services.edges.get_edge(id=edge_id) + edges.append( + Edge( + response_edge.get("id"), + CurveType(response_edge.get("curve_type")), + self._body, + self._grpc_client, + response_edge.get("is_reversed"), + ) ) - for edge in response_loop.get("edges") - ] loops.append( FaceLoop(type=type, length=length, min_bbox=min, max_bbox=max, edges=edges) ) From c7407156d6420a2b8335018d8e62306c93a38b89 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 22 May 2025 09:35:03 +0200 Subject: [PATCH 20/20] fix: unnecessary decorators + key issues --- src/ansys/geometry/core/_grpc/_services/v0/edges.py | 6 +++--- src/ansys/geometry/core/designer/coordinate_system.py | 2 -- src/ansys/geometry/core/designer/design.py | 1 - src/ansys/geometry/core/shapes/curves/trimmed_curve.py | 3 --- src/ansys/geometry/core/tools/problem_areas.py | 1 - 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/edges.py b/src/ansys/geometry/core/_grpc/_services/v0/edges.py index 24c8cef625..36dce751d7 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/edges.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/edges.py @@ -59,9 +59,9 @@ def get_edge(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary return { - "edge_id": response.id, - "edge_curve_type": response.curve_type, - "edge_is_reversed": response.is_reversed, + "id": response.id, + "curve_type": response.curve_type, + "is_reversed": response.is_reversed, } @protect_grpc diff --git a/src/ansys/geometry/core/designer/coordinate_system.py b/src/ansys/geometry/core/designer/coordinate_system.py index edd63f6833..6215cf9fa3 100644 --- a/src/ansys/geometry/core/designer/coordinate_system.py +++ b/src/ansys/geometry/core/designer/coordinate_system.py @@ -24,7 +24,6 @@ from typing import TYPE_CHECKING from ansys.geometry.core.connection.client import GrpcClient -from ansys.geometry.core.errors import protect_grpc from ansys.geometry.core.math.frame import Frame if TYPE_CHECKING: # pragma: no cover @@ -49,7 +48,6 @@ class CoordinateSystem: Active supporting Geometry service instance for design modeling. """ - @protect_grpc def __init__( self, name: str, diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 0c2f5ccf6b..9b5ef22186 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -212,7 +212,6 @@ def _activate(self, called_after_design_creation: bool = False) -> None: # TODO: allow for list of materials # https://github.com/ansys/pyansys-geometry/issues/1319 - @protect_grpc @check_input_types @ensure_design_is_active def add_material(self, material: Material) -> None: diff --git a/src/ansys/geometry/core/shapes/curves/trimmed_curve.py b/src/ansys/geometry/core/shapes/curves/trimmed_curve.py index 8498eb58b9..89360643fa 100644 --- a/src/ansys/geometry/core/shapes/curves/trimmed_curve.py +++ b/src/ansys/geometry/core/shapes/curves/trimmed_curve.py @@ -27,7 +27,6 @@ from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.connection.conversions import trimmed_curve_to_grpc_trimmed_curve -from ansys.geometry.core.errors import protect_grpc from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.misc.measurements import DEFAULT_UNITS from ansys.geometry.core.shapes.curves.curve import Curve @@ -92,13 +91,11 @@ def end(self) -> Point3D: return self._end @property - @protect_grpc def length(self) -> Quantity: """Calculated length of the edge.""" return self._length @property - @protect_grpc def interval(self) -> Interval: """Interval of the curve that provides its boundary.""" return self._interval diff --git a/src/ansys/geometry/core/tools/problem_areas.py b/src/ansys/geometry/core/tools/problem_areas.py index 2b769f149d..b7ec80f2b0 100644 --- a/src/ansys/geometry/core/tools/problem_areas.py +++ b/src/ansys/geometry/core/tools/problem_areas.py @@ -646,7 +646,6 @@ def face_ids(self) -> list[str]: """The ids of the faces defining the logos.""" return self._face_ids - @protect_grpc def fix(self) -> bool: """Fix the problem area by deleting the logos.