Skip to content

Commit c67fc38

Browse files
authored
Merge branch 'blitz' into feat/commands_for_merge_and_intersect
2 parents 1086e2f + 78bde4c commit c67fc38

File tree

8 files changed

+323
-24
lines changed

8 files changed

+323
-24
lines changed

doc/changelog.d/1698.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export and download stride format

src/ansys/geometry/core/designer/design.py

Lines changed: 150 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@
2929
from google.protobuf.empty_pb2 import Empty
3030
import numpy as np
3131
from pint import Quantity, UndefinedUnitError
32+
import semver
3233

3334
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier, PartExportFormat
34-
from ansys.api.dbu.v0.designs_pb2 import InsertRequest, NewRequest, SaveAsRequest
35+
from ansys.api.dbu.v0.designs_pb2 import (
36+
DownloadExportFileRequest,
37+
InsertRequest,
38+
NewRequest,
39+
SaveAsRequest,
40+
)
3541
from ansys.api.dbu.v0.designs_pb2_grpc import DesignsStub
3642
from ansys.api.dbu.v0.drivingdimensions_pb2 import GetAllRequest, UpdateRequest
3743
from ansys.api.dbu.v0.drivingdimensions_pb2_grpc import DrivingDimensionsStub
@@ -84,13 +90,15 @@
8490
class DesignFileFormat(Enum):
8591
"""Provides supported file formats that can be downloaded for designs."""
8692

87-
SCDOCX = "SCDOCX", None
93+
SCDOCX = "SCDOCX", PartExportFormat.PARTEXPORTFORMAT_SCDOCX
8894
PARASOLID_TEXT = "PARASOLID_TEXT", PartExportFormat.PARTEXPORTFORMAT_PARASOLID_TEXT
8995
PARASOLID_BIN = "PARASOLID_BIN", PartExportFormat.PARTEXPORTFORMAT_PARASOLID_BINARY
9096
FMD = "FMD", PartExportFormat.PARTEXPORTFORMAT_FMD
9197
STEP = "STEP", PartExportFormat.PARTEXPORTFORMAT_STEP
9298
IGES = "IGES", PartExportFormat.PARTEXPORTFORMAT_IGES
9399
PMDB = "PMDB", PartExportFormat.PARTEXPORTFORMAT_PMDB
100+
STRIDE = "STRIDE", PartExportFormat.PARTEXPORTFORMAT_STRIDE
101+
DISCO = "DISCO", PartExportFormat.PARTEXPORTFORMAT_DISCO
94102
INVALID = "INVALID", None
95103

96104

@@ -294,6 +302,44 @@ def download(
294302
# Create the parent directory
295303
file_location.parent.mkdir(parents=True, exist_ok=True)
296304

305+
# Process response
306+
self._grpc_client.log.debug(f"Requesting design download in {format.value[0]} format.")
307+
if self._modeler.client.backend_version < semver.Version(25, 2, 0):
308+
received_bytes = self.__export_and_download_legacy(format=format)
309+
else:
310+
received_bytes = self.__export_and_download(format=format)
311+
312+
# Write to file
313+
downloaded_file = Path(file_location).open(mode="wb")
314+
downloaded_file.write(received_bytes)
315+
downloaded_file.close()
316+
317+
self._grpc_client.log.debug(
318+
f"Design is successfully downloaded at location {file_location}."
319+
)
320+
321+
@protect_grpc
322+
@check_input_types
323+
@ensure_design_is_active
324+
def __export_and_download_legacy(
325+
self,
326+
format: DesignFileFormat = DesignFileFormat.SCDOCX,
327+
) -> bytes:
328+
"""Export and download the design from the server.
329+
330+
This is a legacy method, which used in versions
331+
up to Ansys 25.1.1 products.
332+
333+
Parameters
334+
----------
335+
format : DesignFileFormat, default: DesignFileFormat.SCDOCX
336+
Format for the file to save to.
337+
338+
Returns
339+
-------
340+
bytes
341+
The raw data from the exported and downloaded file.
342+
"""
297343
# Process response
298344
self._grpc_client.log.debug(f"Requesting design download in {format.value[0]} format.")
299345
received_bytes = bytes()
@@ -316,14 +362,53 @@ def download(
316362
)
317363
return
318364

319-
# Write to file
320-
downloaded_file = Path(file_location).open(mode="wb")
321-
downloaded_file.write(received_bytes)
322-
downloaded_file.close()
365+
return received_bytes
323366

324-
self._grpc_client.log.debug(
325-
f"Design is successfully downloaded at location {file_location}."
326-
)
367+
@protect_grpc
368+
@check_input_types
369+
@ensure_design_is_active
370+
def __export_and_download(
371+
self,
372+
format: DesignFileFormat = DesignFileFormat.SCDOCX,
373+
) -> bytes:
374+
"""Export and download the design from the server.
375+
376+
Parameters
377+
----------
378+
format : DesignFileFormat, default: DesignFileFormat.SCDOCX
379+
Format for the file to save to.
380+
381+
Returns
382+
-------
383+
bytes
384+
The raw data from the exported and downloaded file.
385+
"""
386+
# Process response
387+
self._grpc_client.log.debug(f"Requesting design download in {format.value[0]} format.")
388+
received_bytes = bytes()
389+
390+
if format in [
391+
DesignFileFormat.PARASOLID_TEXT,
392+
DesignFileFormat.PARASOLID_BIN,
393+
DesignFileFormat.FMD,
394+
DesignFileFormat.STEP,
395+
DesignFileFormat.IGES,
396+
DesignFileFormat.PMDB,
397+
DesignFileFormat.DISCO,
398+
DesignFileFormat.SCDOCX,
399+
DesignFileFormat.STRIDE,
400+
]:
401+
response = self._design_stub.DownloadExportFile(
402+
DownloadExportFileRequest(format=format.value[1])
403+
)
404+
received_bytes += response.data
405+
else:
406+
self._grpc_client.log.warning(
407+
f"{format.value[0]} format requested is not supported. Ignoring download request."
408+
)
409+
return
410+
411+
return received_bytes
327412

328413
def __build_export_file_location(self, location: Path | str | None, ext: str) -> Path:
329414
"""Build the file location for export functions.
@@ -366,6 +451,52 @@ def export_to_scdocx(self, location: Path | str | None = None) -> Path:
366451
# Return the file location
367452
return file_location
368453

454+
def export_to_disco(self, location: Path | str | None = None) -> Path:
455+
"""Export the design to an dsco file.
456+
457+
Parameters
458+
----------
459+
location : ~pathlib.Path | str, optional
460+
Location on disk to save the file to. If None, the file will be saved
461+
in the current working directory.
462+
463+
Returns
464+
-------
465+
~pathlib.Path
466+
The path to the saved file.
467+
"""
468+
# Define the file location
469+
file_location = self.__build_export_file_location(location, "dsco")
470+
471+
# Export the design to an dsco file
472+
self.download(file_location, DesignFileFormat.DISCO)
473+
474+
# Return the file location
475+
return file_location
476+
477+
def export_to_stride(self, location: Path | str | None = None) -> Path:
478+
"""Export the design to an stride file.
479+
480+
Parameters
481+
----------
482+
location : ~pathlib.Path | str, optional
483+
Location on disk to save the file to. If None, the file will be saved
484+
in the current working directory.
485+
486+
Returns
487+
-------
488+
~pathlib.Path
489+
The path to the saved file.
490+
"""
491+
# Define the file location
492+
file_location = self.__build_export_file_location(location, "stride")
493+
494+
# Export the design to an stride file
495+
self.download(file_location, DesignFileFormat.STRIDE)
496+
497+
# Return the file location
498+
return file_location
499+
369500
def export_to_parasolid_text(self, location: Path | str | None = None) -> Path:
370501
"""Export the design to a Parasolid text file.
371502
@@ -381,7 +512,11 @@ def export_to_parasolid_text(self, location: Path | str | None = None) -> Path:
381512
The path to the saved file.
382513
"""
383514
# Determine the extension based on the backend type
384-
ext = "x_t" if self._grpc_client.backend_type == BackendType.LINUX_SERVICE else "xmt_txt"
515+
ext = (
516+
"x_t"
517+
if self._grpc_client.backend_type in (BackendType.LINUX_SERVICE, BackendType.CORE_LINUX)
518+
else "xmt_txt"
519+
)
385520

386521
# Define the file location
387522
file_location = self.__build_export_file_location(location, ext)
@@ -407,7 +542,11 @@ def export_to_parasolid_bin(self, location: Path | str | None = None) -> Path:
407542
The path to the saved file.
408543
"""
409544
# Determine the extension based on the backend type
410-
ext = "x_b" if self._grpc_client.backend_type == BackendType.LINUX_SERVICE else "xmt_bin"
545+
ext = (
546+
"x_b"
547+
if self._grpc_client.backend_type in (BackendType.LINUX_SERVICE, BackendType.CORE_LINUX)
548+
else "xmt_bin"
549+
)
411550

412551
# Define the file location
413552
file_location = self.__build_export_file_location(location, ext)

tests/integration/conftest.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ def skip_if_core_service(modeler: Modeler, test_name: str, element_not_available
5757
) # skip!
5858

5959

60+
def skip_if_windows(modeler: Modeler, test_name: str, element_not_available: str):
61+
"""Skip test if running on Linux."""
62+
if modeler.client.backend_type in (
63+
BackendType.SPACECLAIM,
64+
BackendType.WINDOWS_SERVICE,
65+
BackendType.DISCOVERY,
66+
):
67+
pytest.skip(
68+
reason=f"Skipping '{test_name}'. '{element_not_available}' not on Windows services."
69+
) # skip!
70+
71+
72+
def skip_if_spaceclaim(modeler: Modeler, test_name: str, element_not_available: str):
73+
"""Skip test if running on SpaceClaim."""
74+
if modeler.client.backend_type == BackendType.SPACECLAIM:
75+
pytest.skip(
76+
reason=f"Skipping '{test_name}'. '{element_not_available}' not on SpaceClaim."
77+
) # skip!
78+
79+
6080
@pytest.fixture(scope="session")
6181
def docker_instance(use_existing_service):
6282
# This will only have a value in case that:
7.72 KB
Binary file not shown.

tests/integration/test_design.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,11 @@ def test_download_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactor
974974
binary_parasolid_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.x_b"
975975
text_parasolid_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.x_t"
976976

977+
# FMD
978+
fmd_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.fmd"
979+
design.download(fmd_file, format=DesignFileFormat.FMD)
980+
assert fmd_file.exists()
981+
977982
# Windows-only HOOPS exports for now
978983
step_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.stp"
979984
design.download(step_file, format=DesignFileFormat.STEP)

tests/integration/test_design_export.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from ansys.geometry.core.math import Plane, Point2D, Point3D, UnitVector3D, Vector3D
3131
from ansys.geometry.core.sketch import Sketch
3232

33-
from .conftest import skip_if_core_service
33+
from .conftest import skip_if_core_service, skip_if_spaceclaim, skip_if_windows
3434

3535

3636
def _create_demo_design(modeler: Modeler) -> Design:
@@ -79,6 +79,46 @@ def _create_demo_design(modeler: Modeler) -> Design:
7979
return design
8080

8181

82+
def _create_flat_design(modeler: Modeler) -> Design:
83+
"""Create a demo design for the tests."""
84+
modeler.create_design("Demo")
85+
86+
design_name = "DemoFlatDesign"
87+
design = modeler.create_design(design_name)
88+
89+
# Create a car
90+
comp1 = design.add_component("A")
91+
wheel1 = design.add_component("Wheel1")
92+
93+
# Create car base frame
94+
sketch = Sketch().box(Point2D([5, 10]), 10, 20)
95+
design.add_component("Base").extrude_sketch("BaseBody", sketch, 5)
96+
97+
# Create first wheel
98+
sketch = Sketch(Plane(direction_x=Vector3D([0, 1, 0]), direction_y=Vector3D([0, 0, 1])))
99+
sketch.circle(Point2D([0, 0]), 5)
100+
wheel1.extrude_sketch("Wheel", sketch, -5)
101+
102+
# Create 3 other wheels and move them into position
103+
rotation_origin = Point3D([0, 0, 0])
104+
rotation_direction = UnitVector3D([0, 0, 1])
105+
106+
wheel2 = design.add_component("Wheel2", wheel1)
107+
wheel2.modify_placement(Vector3D([0, 20, 0]))
108+
109+
wheel3 = design.add_component("Wheel3", wheel1)
110+
wheel3.modify_placement(Vector3D([10, 0, 0]), rotation_origin, rotation_direction, np.pi)
111+
112+
wheel4 = design.add_component("Wheel4", wheel1)
113+
wheel4.modify_placement(Vector3D([10, 20, 0]), rotation_origin, rotation_direction, np.pi)
114+
115+
# Create top of car - applies to BOTH cars
116+
sketch = Sketch(Plane(Point3D([0, 5, 5]))).box(Point2D([5, 2.5]), 10, 5)
117+
comp1.extrude_sketch("Top", sketch, 5)
118+
119+
return design
120+
121+
82122
def _checker_method(comp: Component, comp_ref: Component, precise_check: bool = True) -> None:
83123
# Check component features
84124
if precise_check:
@@ -132,6 +172,52 @@ def test_export_to_scdocx(modeler: Modeler, tmp_path_factory: pytest.TempPathFac
132172
_checker_method(design_read, design, True)
133173

134174

175+
def test_export_to_stride(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
176+
"""Test exporting a design to stride format."""
177+
skip_if_windows(modeler, test_export_to_stride.__name__, "design") # Skip test on SC/DMS
178+
# Create a demo design
179+
design = _create_flat_design(modeler)
180+
181+
# Define the location and expected file location
182+
location = tmp_path_factory.mktemp("test_export_to_stride")
183+
file_location = location / f"{design.name}.stride"
184+
185+
# Export to stride
186+
design.export_to_stride(location)
187+
188+
# Check the exported file
189+
assert file_location.exists()
190+
191+
# Import the stride
192+
design_read = modeler.open_file(file_location)
193+
194+
# Check the imported design
195+
_checker_method(design_read, design, False)
196+
197+
198+
def test_export_to_disco(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
199+
"""Test exporting a design to dsco format."""
200+
skip_if_spaceclaim(modeler, test_export_to_disco.__name__, "disco export")
201+
# Create a demo design
202+
design = _create_demo_design(modeler)
203+
204+
# Define the location and expected file location
205+
location = tmp_path_factory.mktemp("test_export_to_disco")
206+
file_location = location / f"{design.name}.dsco"
207+
208+
# Export to dsco
209+
design.export_to_disco(location)
210+
211+
# Check the exported file
212+
assert file_location.exists()
213+
214+
# Import the dsco
215+
design_read = modeler.open_file(file_location)
216+
217+
# Check the imported design
218+
_checker_method(design_read, design, True)
219+
220+
135221
def test_export_to_parasolid_text(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
136222
"""Test exporting a design to parasolid text format."""
137223
# Create a demo design
@@ -205,6 +291,7 @@ def test_export_to_step(modeler: Modeler, tmp_path_factory: pytest.TempPathFacto
205291
def test_export_to_iges(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
206292
"""Test exporting a design to IGES format."""
207293
skip_if_core_service(modeler, test_export_to_iges.__name__, "iges_export")
294+
208295
# Create a demo design
209296
design = _create_demo_design(modeler)
210297

@@ -225,6 +312,7 @@ def test_export_to_iges(modeler: Modeler, tmp_path_factory: pytest.TempPathFacto
225312
def test_export_to_fmd(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
226313
"""Test exporting a design to FMD format."""
227314
skip_if_core_service(modeler, test_export_to_fmd.__name__, "fmd_export")
315+
228316
# Create a demo design
229317
design = _create_demo_design(modeler)
230318

0 commit comments

Comments
 (0)