From d36d3d13fdb62f95f5c5a484ba774fe6144294e8 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 12 Aug 2025 11:08:15 -0500 Subject: [PATCH 1/6] option to write body facets --- pyproject.toml | 2 +- .../core/_grpc/_services/v0/designs.py | 10 ++++-- src/ansys/geometry/core/designer/design.py | 19 +++++++++--- tests/integration/test_design.py | 31 +++++++++++++++++++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a20101adad..54794f8a46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-geometry==0.4.69", + "ansys-api-geometry==0.4.71", "ansys-tools-path>=0.3,<1", "beartype>=0.11.0,<0.22", "geomdl>=5,<6", diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py index 6fec2e0110..9b2146a5b6 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -107,7 +107,9 @@ 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"]) + request = SaveAsRequest( + filepath=kwargs["filepath"], write_body_facets=["write_body_facets"] + ) # Call the gRPC service _ = self.stub.SaveAs(request) @@ -121,7 +123,8 @@ def download_export(self, **kwargs) -> dict: # noqa: D102 # 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"]) + format=from_design_file_format_to_grpc_part_export_format(kwargs["format"]), + write_body_facets=kwargs["write_body_facets"], ) # Call the gRPC service @@ -138,7 +141,8 @@ def stream_download_export(self, **kwargs) -> dict: # noqa: D102 # 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"]) + format=from_design_file_format_to_grpc_part_export_format(kwargs["format"]), + write_body_facets=kwargs["write_body_facets"], ) # Call the gRPC service diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 74f6f59f77..a272f7d269 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -251,6 +251,7 @@ def download( self, file_location: Path | str, format: DesignFileFormat = DesignFileFormat.SCDOCX, + write_body_facets: bool = False, ) -> None: """Export and download the design from the server. @@ -275,7 +276,9 @@ def download( if self._modeler.client.backend_version < (25, 2, 0): received_bytes = self.__export_and_download_legacy(format=format) else: - received_bytes = self.__export_and_download(format=format) + received_bytes = self.__export_and_download( + format=format, write_body_facets=write_body_facets + ) # Write to file file_location.write_bytes(received_bytes) @@ -323,7 +326,11 @@ def __export_and_download_legacy(self, format: DesignFileFormat) -> bytes: return received_bytes - def __export_and_download(self, format: DesignFileFormat) -> bytes: + def __export_and_download( + self, + format: DesignFileFormat, + write_body_facets: bool = False, + ) -> bytes: """Export and download the design from the server. Parameters @@ -351,14 +358,18 @@ def __export_and_download(self, format: DesignFileFormat) -> bytes: DesignFileFormat.STRIDE, ]: try: - response = self._grpc_client.services.designs.download_export(format=format) + response = self._grpc_client.services.designs.download_export( + format=format, write_body_facets=write_body_facets + ) except Exception: self._grpc_client.log.warning( f"Failed to download the file in {format} format." " Attempting to stream download." ) # Attempt to download the file via streaming - response = self._grpc_client.services.designs.stream_download_export(format=format) + response = self._grpc_client.services.designs.stream_download_export( + format=format, write_body_facets=write_body_facets + ) else: self._grpc_client.log.warning( f"{format} format requested is not supported. Ignoring download request." diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index c05e53ecf0..17c2a5c3ba 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -23,6 +23,7 @@ import os from pathlib import Path +import zipfile import matplotlib.colors as mcolors import numpy as np @@ -3579,3 +3580,33 @@ def test_component_make_independent(modeler: Modeler): comp = design.components[0].components[-1].components[-1] # stale from update-design-in-place assert not Accuracy.length_is_equal(comp.bodies[0].volume.m, face.body.volume.m) + + +def test_write_body_facets_on_save(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): + design = modeler.open_file(Path(FILES_DIR, "cars.scdocx")) + + # First file without body facets + filepath_no_facets = tmp_path_factory.mktemp("test_design") / "cars_no_facets.scdocx" + design.download(filepath_no_facets) + + # Second file with body facets + filepath_with_facets = tmp_path_factory.mktemp("test_design") / "cars_with_facets.scdocx" + design.download(filepath_with_facets, write_body_facets=True) + + # Compare file sizes + size_no_facets = filepath_no_facets.stat().st_size + size_with_facets = filepath_with_facets.stat().st_size + + assert size_with_facets > size_no_facets + + # Ensure facets.bin and renderlist.xml files exist + with zipfile.ZipFile(filepath_with_facets, "r") as zip_ref: + namelist = set(zip_ref.namelist()) + + expected_files = { + "SpaceClaim/Graphics/facets.bin", + "SpaceClaim/Graphics/renderlist.xml", + } + + missing = expected_files - namelist + assert not missing From 6336676d71165ec528df0ee3d4304d3fb92c86cd Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:11:48 +0000 Subject: [PATCH 2/6] chore: adding changelog file 2169.added.md [dependabot-skip] --- doc/changelog.d/2169.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/2169.added.md diff --git a/doc/changelog.d/2169.added.md b/doc/changelog.d/2169.added.md new file mode 100644 index 0000000000..a6eaa99541 --- /dev/null +++ b/doc/changelog.d/2169.added.md @@ -0,0 +1 @@ +Option to write body facets on save From f708f8d0d7a780477b972ab06aa039ef9ac349fd Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 12 Aug 2025 11:36:09 -0500 Subject: [PATCH 3/6] docstrings --- src/ansys/geometry/core/designer/design.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index a272f7d269..eb1cf6256f 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -229,19 +229,23 @@ def add_material(self, material: Material) -> None: @check_input_types @ensure_design_is_active - def save(self, file_location: Path | str) -> None: + def save(self, file_location: Path | str, write_body_facets: bool = False) -> None: """Save a design to disk on the active Geometry server instance. Parameters ---------- file_location : ~pathlib.Path | str Location on disk to save the file to. + write_body_facets : bool, default: False + Option to write body facets into the saved file. 26R1 and later. """ # Sanity checks on inputs if isinstance(file_location, Path): file_location = str(file_location) - self._grpc_client.services.designs.save_as(filepath=file_location) + self._grpc_client.services.designs.save_as( + filepath=file_location, write_body_facets=write_body_facets + ) self._grpc_client.log.debug(f"Design successfully saved at location {file_location}.") @protect_grpc @@ -261,6 +265,8 @@ def download( Location on disk to save the file to. format : DesignFileFormat, default: DesignFileFormat.SCDOCX Format for the file to save to. + write_body_facets : bool, default: False + Option to write body facets into the saved file. SCDOCX only, 26R1 and later. """ # Sanity checks on inputs if isinstance(file_location, str): From 2b95a289bda26f40f563f26d3a5f973b6c1f0ba9 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 12 Aug 2025 11:49:44 -0500 Subject: [PATCH 4/6] fix save_as --- 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 9b2146a5b6..f2a8ba47f7 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -108,7 +108,7 @@ def save_as(self, **kwargs) -> dict: # noqa: D102 # Create the request - assumes all inputs are valid and of the proper type request = SaveAsRequest( - filepath=kwargs["filepath"], write_body_facets=["write_body_facets"] + filepath=kwargs["filepath"], write_body_facets=kwargs["write_body_facets"] ) # Call the gRPC service From 8e9048b84d1beda24d63b0977b30c26a9828e3f6 Mon Sep 17 00:00:00 2001 From: rward Date: Tue, 12 Aug 2025 14:03:45 -0400 Subject: [PATCH 5/6] skip tests for older versions --- tests/_incompatible_tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/_incompatible_tests.yml b/tests/_incompatible_tests.yml index 79ee74d704..c073518bcd 100644 --- a/tests/_incompatible_tests.yml +++ b/tests/_incompatible_tests.yml @@ -102,6 +102,8 @@ backends: # Bug fix included from 26.1 onwards - tests/integration/test_design_import.py::test_named_selections_after_file_insert - tests/integration/test_design_import.py::test_named_selections_after_file_open + # Export body facets add in 26.1 + - tests/integration/test_design.py::test_write_body_facets_on_save - version: "24.2" incompatible_tests: @@ -198,6 +200,8 @@ backends: # Bug fix included from 26.1 onwards - tests/integration/test_design_import.py::test_named_selections_after_file_insert - tests/integration/test_design_import.py::test_named_selections_after_file_open + # Export body facets add in 26.1 + - tests/integration/test_design.py::test_write_body_facets_on_save - version: "25.1" incompatible_tests: @@ -263,6 +267,8 @@ backends: # Bug fix included from 26.1 onwards - tests/integration/test_design_import.py::test_named_selections_after_file_insert - tests/integration/test_design_import.py::test_named_selections_after_file_open + # Export body facets add in 26.1 + - tests/integration/test_design.py::test_write_body_facets_on_save - version: "25.2" incompatible_tests: @@ -292,3 +298,5 @@ backends: # Bug fix included from 26.1 onwards - tests/integration/test_design_import.py::test_named_selections_after_file_insert - tests/integration/test_design_import.py::test_named_selections_after_file_open + # Export body facets add in 26.1 + - tests/integration/test_design.py::test_write_body_facets_on_save From 117b6448252fbefdd3ffaac8276df9249e361dc0 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Wed, 13 Aug 2025 08:50:31 -0500 Subject: [PATCH 6/6] warn on pre-26R1 --- .../core/_grpc/_services/v0/conversions.py | 20 +++++++++++++++++++ .../core/_grpc/_services/v0/designs.py | 12 ++++++++++- src/ansys/geometry/core/designer/design.py | 12 ++++++++--- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py index b211720bda..f7dc96063e 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/conversions.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/conversions.py @@ -1161,3 +1161,23 @@ def _nurbs_curves_compatibility(backend_version: "semver.Version", grpc_geometri + "26.1.0, but the current version used is " + f"{backend_version}." ) + + +def _check_write_body_facets_input(backend_version: "semver.Version", write_body_facets: bool): + """Check if the backend version is compatible with NURBS curves in sketches. + + Parameters + ---------- + backend_version : semver.Version + The version of the backend. + write_body_facets : bool + Option to write out body facets. + """ + if write_body_facets and backend_version < (26, 1, 0): + from ansys.geometry.core.logger import LOG + + LOG.warning( + "The usage of write_body_facets requires a minimum Ansys release version of " + + "26.1.0, but the current version used is " + + f"{backend_version}." + ) diff --git a/src/ansys/geometry/core/_grpc/_services/v0/designs.py b/src/ansys/geometry/core/_grpc/_services/v0/designs.py index f2a8ba47f7..b74961cfc7 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/designs.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/designs.py @@ -26,7 +26,11 @@ 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 +from .conversions import ( + _check_write_body_facets_input, + build_grpc_id, + from_design_file_format_to_grpc_part_export_format, +) class GRPCDesignsServiceV0(GRPCDesignsService): # pragma: no cover @@ -106,6 +110,8 @@ def put_active(self, **kwargs) -> dict: # noqa: D102 def save_as(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import SaveAsRequest + _check_write_body_facets_input(kwargs["backend_version"], kwargs["write_body_facets"]) + # Create the request - assumes all inputs are valid and of the proper type request = SaveAsRequest( filepath=kwargs["filepath"], write_body_facets=kwargs["write_body_facets"] @@ -121,6 +127,8 @@ def save_as(self, **kwargs) -> dict: # noqa: D102 def download_export(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import DownloadExportFileRequest + _check_write_body_facets_input(kwargs["backend_version"], kwargs["write_body_facets"]) + # 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"]), @@ -139,6 +147,8 @@ def download_export(self, **kwargs) -> dict: # noqa: D102 def stream_download_export(self, **kwargs) -> dict: # noqa: D102 from ansys.api.dbu.v0.designs_pb2 import DownloadExportFileRequest + _check_write_body_facets_input(kwargs["backend_version"], kwargs["write_body_facets"]) + # 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"]), diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index eb1cf6256f..8406ad4648 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -244,7 +244,9 @@ def save(self, file_location: Path | str, write_body_facets: bool = False) -> No file_location = str(file_location) self._grpc_client.services.designs.save_as( - filepath=file_location, write_body_facets=write_body_facets + filepath=file_location, + write_body_facets=write_body_facets, + backend_version=self._grpc_client.backend_version, ) self._grpc_client.log.debug(f"Design successfully saved at location {file_location}.") @@ -365,7 +367,9 @@ def __export_and_download( ]: try: response = self._grpc_client.services.designs.download_export( - format=format, write_body_facets=write_body_facets + format=format, + write_body_facets=write_body_facets, + backend_version=self._grpc_client.backend_version, ) except Exception: self._grpc_client.log.warning( @@ -374,7 +378,9 @@ def __export_and_download( ) # Attempt to download the file via streaming response = self._grpc_client.services.designs.stream_download_export( - format=format, write_body_facets=write_body_facets + format=format, + write_body_facets=write_body_facets, + backend_version=self._grpc_client.backend_version, ) else: self._grpc_client.log.warning(