Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: export and download stride format #1698

Merged
merged 43 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1a7c83b
Update pyproject.toml
b-matteo Jan 20, 2025
9b26efa
Update design.py
b-matteo Jan 20, 2025
2679cca
Update design.py
b-matteo Jan 22, 2025
8b36e83
Add new BackendType and tests for stride and disco exports
b-matteo Jan 23, 2025
0585539
Update client.py
b-matteo Jan 23, 2025
7505ef0
Merge branch 'blitz' into feat/download_stride
b-matteo Jan 23, 2025
9c956d3
"from ansys.geometry.core.designer.body import Body" locally;
mlkaplan36 Jan 23, 2025
d66fd3f
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 23, 2025
28f34e2
chore: adding changelog file 1683.added.md [dependabot-skip]
pyansys-ci-bot Jan 23, 2025
41ff65d
chore: adding changelog file 1683.test.md [dependabot-skip]
pyansys-ci-bot Jan 23, 2025
29193ef
Rename test and add new test
RyanJWard Jan 23, 2025
e8af79c
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 23, 2025
5a5bf8c
Remove unnecessary parameter
mlkaplan36 Jan 23, 2025
04ffdbd
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 23, 2025
dd980bd
More tests
RyanJWard Jan 23, 2025
dc0df4e
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 23, 2025
e012f7c
remove "result" from response since the message isn't nested
mlkaplan36 Jan 23, 2025
40f90be
Merge branch 'fix/import_body_locally' of https://github.com/ansys/py…
mlkaplan36 Jan 23, 2025
d7faba3
Fix input values
RyanJWard Jan 23, 2025
89ceb99
put 139 instead of 138
RyanJWard Jan 23, 2025
4616212
Add tests for stride format
b-matteo Jan 23, 2025
42c5906
Update test_design_import.py
b-matteo Jan 24, 2025
6c53165
Further test fix and pre-commit
b-matteo Jan 24, 2025
3f3b862
Merge branch 'fix/test_open_file_filtering_stride' into feat/download…
b-matteo Jan 24, 2025
db1ca88
Invert parameters order to match the one in the backend
b-matteo Jan 24, 2025
9a87fbb
Merge branch 'fix/fix_split_edges_test' into feat/download_stride
b-matteo Jan 24, 2025
9726c4f
Enable real test for export id after CoreService update
b-matteo Jan 24, 2025
3b24631
Avoid testing Disco export against SC
b-matteo Jan 24, 2025
149b46d
Merge branch 'fix/import_body_locally' into feat/download_stride
b-matteo Jan 24, 2025
8c8d41f
undo changes done in other branches
b-matteo Jan 28, 2025
d27f7e2
Undo changes made in other branches
b-matteo Jan 28, 2025
76d4eb5
Merge branch 'blitz' into feat/download_stride
b-matteo Jan 28, 2025
c507b2d
pre commit
b-matteo Jan 28, 2025
35c0f09
chore: adding changelog file 1698.added.md [dependabot-skip]
pyansys-ci-bot Jan 28, 2025
2dd5878
Merge branch 'blitz' into feat/download_stride
RobPasMue Jan 28, 2025
3bf9de5
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 28, 2025
5d6781c
fix: cleanup
RobPasMue Jan 28, 2025
a3fefbf
Merge branch 'feat/download_stride' of https://github.com/ansys/pyans…
RobPasMue Jan 28, 2025
1bccc2b
PR feedbacks
b-matteo Jan 28, 2025
4b12ffd
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 28, 2025
972ec03
Fix docstrings
b-matteo Jan 28, 2025
cc46678
Merge branch 'feat/download_stride' of https://github.com/ansys/pyans…
b-matteo Jan 28, 2025
cdca2d9
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/1698.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export and download stride format
161 changes: 150 additions & 11 deletions src/ansys/geometry/core/designer/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@
from google.protobuf.empty_pb2 import Empty
import numpy as np
from pint import Quantity, UndefinedUnitError
import semver

from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier, PartExportFormat
from ansys.api.dbu.v0.designs_pb2 import InsertRequest, NewRequest, SaveAsRequest
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.drivingdimensions_pb2 import GetAllRequest, UpdateRequest
from ansys.api.dbu.v0.drivingdimensions_pb2_grpc import DrivingDimensionsStub
Expand Down Expand Up @@ -84,13 +90,15 @@
class DesignFileFormat(Enum):
"""Provides supported file formats that can be downloaded for designs."""

SCDOCX = "SCDOCX", None
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


Expand Down Expand Up @@ -294,6 +302,44 @@ def download(
# Create the parent directory
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.")
if self._modeler.client.backend_version < semver.Version(25, 2, 0):
received_bytes = self.__export_and_download_legacy(format=format)
else:
received_bytes = self.__export_and_download(format=format)

# Write to file
downloaded_file = Path(file_location).open(mode="wb")
downloaded_file.write(received_bytes)
downloaded_file.close()

self._grpc_client.log.debug(
f"Design is successfully downloaded at location {file_location}."
)

@protect_grpc
@check_input_types
@ensure_design_is_active
def __export_and_download_legacy(
self,
format: DesignFileFormat = DesignFileFormat.SCDOCX,
) -> bytes:
"""Export and download the design from the server.

This is a legacy method, which used in versions
up to Ansys 25.1.1 products.

Parameters
----------
format : DesignFileFormat, default: DesignFileFormat.SCDOCX
Format for the file to save to.

Returns
-------
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()
Expand All @@ -316,14 +362,53 @@ def download(
)
return

# Write to file
downloaded_file = Path(file_location).open(mode="wb")
downloaded_file.write(received_bytes)
downloaded_file.close()
return received_bytes

self._grpc_client.log.debug(
f"Design is successfully downloaded at location {file_location}."
)
@protect_grpc
@check_input_types
@ensure_design_is_active
def __export_and_download(
self,
format: DesignFileFormat = DesignFileFormat.SCDOCX,
) -> bytes:
"""Export and download the design from the server.

Parameters
----------
format : DesignFileFormat, default: DesignFileFormat.SCDOCX
Format for the file to save to.

Returns
-------
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()

if format in [
DesignFileFormat.PARASOLID_TEXT,
DesignFileFormat.PARASOLID_BIN,
DesignFileFormat.FMD,
DesignFileFormat.STEP,
DesignFileFormat.IGES,
DesignFileFormat.PMDB,
DesignFileFormat.DISCO,
DesignFileFormat.SCDOCX,
DesignFileFormat.STRIDE,
]:
response = self._design_stub.DownloadExportFile(
DownloadExportFileRequest(format=format.value[1])
)
received_bytes += response.data
else:
self._grpc_client.log.warning(
f"{format.value[0]} format requested is not supported. Ignoring download request."
)
return

return received_bytes

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

def export_to_disco(self, location: Path | str | None = None) -> Path:
"""Export the design to an dsco file.

Parameters
----------
location : ~pathlib.Path | str, optional
Location on disk to save the file to. If None, the file will be saved
in the current working directory.

Returns
-------
~pathlib.Path
The path to the saved file.
"""
# Define the file location
file_location = self.__build_export_file_location(location, "dsco")

# Export the design to an dsco file
self.download(file_location, DesignFileFormat.DISCO)

# Return the file location
return file_location

def export_to_stride(self, location: Path | str | None = None) -> Path:
"""Export the design to an stride file.

Parameters
----------
location : ~pathlib.Path | str, optional
Location on disk to save the file to. If None, the file will be saved
in the current working directory.

Returns
-------
~pathlib.Path
The path to the saved file.
"""
# Define the file location
file_location = self.__build_export_file_location(location, "stride")

# Export the design to an stride file
self.download(file_location, DesignFileFormat.STRIDE)

# Return the file location
return file_location

def export_to_parasolid_text(self, location: Path | str | None = None) -> Path:
"""Export the design to a Parasolid text file.

Expand All @@ -381,7 +512,11 @@ def export_to_parasolid_text(self, location: Path | str | None = None) -> Path:
The path to the saved file.
"""
# Determine the extension based on the backend type
ext = "x_t" if self._grpc_client.backend_type == BackendType.LINUX_SERVICE else "xmt_txt"
ext = (
"x_t"
if self._grpc_client.backend_type in (BackendType.LINUX_SERVICE, BackendType.CORE_LINUX)
else "xmt_txt"
)

# Define the file location
file_location = self.__build_export_file_location(location, ext)
Expand All @@ -407,7 +542,11 @@ def export_to_parasolid_bin(self, location: Path | str | None = None) -> Path:
The path to the saved file.
"""
# Determine the extension based on the backend type
ext = "x_b" if self._grpc_client.backend_type == BackendType.LINUX_SERVICE else "xmt_bin"
ext = (
"x_b"
if self._grpc_client.backend_type in (BackendType.LINUX_SERVICE, BackendType.CORE_LINUX)
else "xmt_bin"
)

# Define the file location
file_location = self.__build_export_file_location(location, ext)
Expand Down
20 changes: 20 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ def skip_if_core_service(modeler: Modeler, test_name: str, element_not_available
) # skip!


def skip_if_windows(modeler: Modeler, test_name: str, element_not_available: str):
"""Skip test if running on Linux."""
if modeler.client.backend_type in (
BackendType.SPACECLAIM,
BackendType.WINDOWS_SERVICE,
BackendType.DISCOVERY,
):
pytest.skip(
reason=f"Skipping '{test_name}'. '{element_not_available}' not on Windows services."
) # skip!


def skip_if_spaceclaim(modeler: Modeler, test_name: str, element_not_available: str):
"""Skip test if running on SpaceClaim."""
if modeler.client.backend_type == BackendType.SPACECLAIM:
pytest.skip(
reason=f"Skipping '{test_name}'. '{element_not_available}' not on SpaceClaim."
) # skip!


@pytest.fixture(scope="session")
def docker_instance(use_existing_service):
# This will only have a value in case that:
Expand Down
Binary file added tests/integration/files/import/twoCars.stride
Binary file not shown.
5 changes: 5 additions & 0 deletions tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,11 @@ def test_download_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactor
binary_parasolid_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.x_b"
text_parasolid_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.x_t"

# FMD
fmd_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.fmd"
design.download(fmd_file, format=DesignFileFormat.FMD)
assert fmd_file.exists()

# Windows-only HOOPS exports for now
step_file = tmp_path_factory.mktemp("scdoc_files_download") / "cylinder.stp"
design.download(step_file, format=DesignFileFormat.STEP)
Expand Down
90 changes: 89 additions & 1 deletion tests/integration/test_design_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from ansys.geometry.core.math import Plane, Point2D, Point3D, UnitVector3D, Vector3D
from ansys.geometry.core.sketch import Sketch

from .conftest import skip_if_core_service
from .conftest import skip_if_core_service, skip_if_spaceclaim, skip_if_windows


def _create_demo_design(modeler: Modeler) -> Design:
Expand Down Expand Up @@ -79,6 +79,46 @@ def _create_demo_design(modeler: Modeler) -> Design:
return design


def _create_flat_design(modeler: Modeler) -> Design:
"""Create a demo design for the tests."""
modeler.create_design("Demo")

design_name = "DemoFlatDesign"
design = modeler.create_design(design_name)

# Create a car
comp1 = design.add_component("A")
wheel1 = design.add_component("Wheel1")

# Create car base frame
sketch = Sketch().box(Point2D([5, 10]), 10, 20)
design.add_component("Base").extrude_sketch("BaseBody", sketch, 5)

# Create first wheel
sketch = Sketch(Plane(direction_x=Vector3D([0, 1, 0]), direction_y=Vector3D([0, 0, 1])))
sketch.circle(Point2D([0, 0]), 5)
wheel1.extrude_sketch("Wheel", sketch, -5)

# Create 3 other wheels and move them into position
rotation_origin = Point3D([0, 0, 0])
rotation_direction = UnitVector3D([0, 0, 1])

wheel2 = design.add_component("Wheel2", wheel1)
wheel2.modify_placement(Vector3D([0, 20, 0]))

wheel3 = design.add_component("Wheel3", wheel1)
wheel3.modify_placement(Vector3D([10, 0, 0]), rotation_origin, rotation_direction, np.pi)

wheel4 = design.add_component("Wheel4", wheel1)
wheel4.modify_placement(Vector3D([10, 20, 0]), rotation_origin, rotation_direction, np.pi)

# Create top of car - applies to BOTH cars
sketch = Sketch(Plane(Point3D([0, 5, 5]))).box(Point2D([5, 2.5]), 10, 5)
comp1.extrude_sketch("Top", sketch, 5)

return design


def _checker_method(comp: Component, comp_ref: Component, precise_check: bool = True) -> None:
# Check component features
if precise_check:
Expand Down Expand Up @@ -132,6 +172,52 @@ def test_export_to_scdocx(modeler: Modeler, tmp_path_factory: pytest.TempPathFac
_checker_method(design_read, design, True)


def test_export_to_stride(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
"""Test exporting a design to stride format."""
skip_if_windows(modeler, test_export_to_stride.__name__, "design") # Skip test on SC/DMS
# Create a demo design
design = _create_flat_design(modeler)

# Define the location and expected file location
location = tmp_path_factory.mktemp("test_export_to_stride")
file_location = location / f"{design.name}.stride"

# Export to stride
design.export_to_stride(location)

# Check the exported file
assert file_location.exists()

# Import the stride
design_read = modeler.open_file(file_location)

# Check the imported design
_checker_method(design_read, design, False)


def test_export_to_disco(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
"""Test exporting a design to dsco format."""
skip_if_spaceclaim(modeler, test_export_to_disco.__name__, "disco export")
# Create a demo design
design = _create_demo_design(modeler)

# Define the location and expected file location
location = tmp_path_factory.mktemp("test_export_to_disco")
file_location = location / f"{design.name}.dsco"

# Export to dsco
design.export_to_disco(location)

# Check the exported file
assert file_location.exists()

# Import the dsco
design_read = modeler.open_file(file_location)

# Check the imported design
_checker_method(design_read, design, True)


def test_export_to_parasolid_text(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
"""Test exporting a design to parasolid text format."""
# Create a demo design
Expand Down Expand Up @@ -205,6 +291,7 @@ def test_export_to_step(modeler: Modeler, tmp_path_factory: pytest.TempPathFacto
def test_export_to_iges(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
"""Test exporting a design to IGES format."""
skip_if_core_service(modeler, test_export_to_iges.__name__, "iges_export")

# Create a demo design
design = _create_demo_design(modeler)

Expand All @@ -225,6 +312,7 @@ def test_export_to_iges(modeler: Modeler, tmp_path_factory: pytest.TempPathFacto
def test_export_to_fmd(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory):
"""Test exporting a design to FMD format."""
skip_if_core_service(modeler, test_export_to_fmd.__name__, "fmd_export")

# Create a demo design
design = _create_demo_design(modeler)

Expand Down
Loading
Loading