Skip to content

Commit 3bea3ed

Browse files
mlkaplan36pyansys-ci-botpre-commit-ci[bot]RobPasMueumutsoysalansys
authored
feat: Streaming upload support (#1779)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Roberto Pastor Muela <[email protected]> Co-authored-by: Umut Soysal <[email protected]>
1 parent 04ffd1b commit 3bea3ed

File tree

13 files changed

+302
-57
lines changed

13 files changed

+302
-57
lines changed

doc/changelog.d/1779.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Streaming upload support

src/ansys/geometry/core/connection/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,7 @@
3838
trimmed_curve_to_grpc_trimmed_curve,
3939
unit_vector_to_grpc_direction,
4040
)
41-
from ansys.geometry.core.connection.defaults import (
42-
DEFAULT_HOST,
43-
DEFAULT_PORT,
44-
GEOMETRY_SERVICE_DOCKER_IMAGE,
45-
)
41+
import ansys.geometry.core.connection.defaults as defaults
4642
from ansys.geometry.core.connection.docker_instance import (
4743
GeometryContainers,
4844
LocalDockerInstance,

src/ansys/geometry/core/connection/client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
)
5555
from ansys.api.dbu.v0.admin_pb2_grpc import AdminStub
5656
from ansys.geometry.core.connection.backend import BackendType
57-
from ansys.geometry.core.connection.defaults import DEFAULT_HOST, DEFAULT_PORT, MAX_MESSAGE_LENGTH
57+
import ansys.geometry.core.connection.defaults as pygeom_defaults
5858
from ansys.geometry.core.connection.docker_instance import LocalDockerInstance
5959
from ansys.geometry.core.connection.product_instance import ProductInstance
6060
from ansys.geometry.core.logger import LOG, PyGeometryCustomAdapter
@@ -158,8 +158,8 @@ class GrpcClient:
158158
@check_input_types
159159
def __init__(
160160
self,
161-
host: str = DEFAULT_HOST,
162-
port: str | int = DEFAULT_PORT,
161+
host: str = pygeom_defaults.DEFAULT_HOST,
162+
port: str | int = pygeom_defaults.DEFAULT_PORT,
163163
channel: grpc.Channel | None = None,
164164
remote_instance: Optional["Instance"] = None,
165165
docker_instance: LocalDockerInstance | None = None,
@@ -183,8 +183,8 @@ def __init__(
183183
self._channel = grpc.insecure_channel(
184184
self._target,
185185
options=[
186-
("grpc.max_receive_message_length", MAX_MESSAGE_LENGTH),
187-
("grpc.max_send_message_length", MAX_MESSAGE_LENGTH),
186+
("grpc.max_receive_message_length", pygeom_defaults.MAX_MESSAGE_LENGTH),
187+
("grpc.max_send_message_length", pygeom_defaults.MAX_MESSAGE_LENGTH),
188188
],
189189
)
190190

src/ansys/geometry/core/connection/docker_instance.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
except ModuleNotFoundError: # pragma: no cover
3838
_HAS_DOCKER = False
3939

40-
from ansys.geometry.core.connection.defaults import DEFAULT_PORT, GEOMETRY_SERVICE_DOCKER_IMAGE
40+
import ansys.geometry.core.connection.defaults as pygeom_defaults
4141
from ansys.geometry.core.logger import LOG
4242

4343

@@ -157,7 +157,7 @@ def is_docker_installed() -> bool:
157157
@_docker_python_available
158158
def __init__(
159159
self,
160-
port: int = DEFAULT_PORT,
160+
port: int = pygeom_defaults.DEFAULT_PORT,
161161
connect_to_existing_service: bool = True,
162162
restart_if_existing_service: bool = False,
163163
name: str | None = None,
@@ -234,7 +234,10 @@ def _is_cont_geom_service(self, cont: "Container") -> bool:
234234
# If one of the tags matches a Geometry service tag --> Return True
235235
for tag in cont.image.tags:
236236
for geom_services in GeometryContainers:
237-
if tag == f"{GEOMETRY_SERVICE_DOCKER_IMAGE}:{geom_services.value[2]}":
237+
if (
238+
tag
239+
== f"{pygeom_defaults.GEOMETRY_SERVICE_DOCKER_IMAGE}:{geom_services.value[2]}"
240+
):
238241
return True
239242

240243
# If you have reached this point, the image is not a Geometry service
@@ -290,7 +293,7 @@ def _deploy_container(self, port: int, name: str | None, image: GeometryContaine
290293
# Try to deploy it
291294
try:
292295
container: Container = self.docker_client().containers.run(
293-
image=f"{GEOMETRY_SERVICE_DOCKER_IMAGE}:{image.value[2]}",
296+
image=f"{pygeom_defaults.GEOMETRY_SERVICE_DOCKER_IMAGE}:{image.value[2]}",
294297
detach=True,
295298
auto_remove=True,
296299
name=name,
@@ -350,7 +353,7 @@ def get_geometry_container_type(instance: LocalDockerInstance) -> GeometryContai
350353
"""
351354
for tag in instance.container.image.tags:
352355
for geom_services in GeometryContainers:
353-
if tag == f"{GEOMETRY_SERVICE_DOCKER_IMAGE}:{geom_services.value[2]}":
356+
if tag == f"{pygeom_defaults.GEOMETRY_SERVICE_DOCKER_IMAGE}:{geom_services.value[2]}":
354357
return geom_services
355358

356359
return None

src/ansys/geometry/core/connection/launcher.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
from typing import TYPE_CHECKING
2828

2929
from ansys.geometry.core.connection.backend import ApiVersions, BackendType
30-
from ansys.geometry.core.connection.client import MAX_MESSAGE_LENGTH
31-
from ansys.geometry.core.connection.defaults import DEFAULT_PIM_CONFIG, DEFAULT_PORT
30+
import ansys.geometry.core.connection.defaults as pygeom_defaults
3231
from ansys.geometry.core.connection.docker_instance import (
3332
_HAS_DOCKER,
3433
GeometryContainers,
@@ -290,7 +289,7 @@ def launch_remote_modeler(
290289

291290

292291
def launch_docker_modeler(
293-
port: int = DEFAULT_PORT,
292+
port: int = pygeom_defaults.DEFAULT_PORT,
294293
connect_to_existing_service: bool = True,
295294
restart_if_existing_service: bool = False,
296295
name: str | None = None,
@@ -958,7 +957,7 @@ def _launch_pim_instance(
958957

959958
# If PIM Light is being used and PyPIM configuration is not defined... use defaults.
960959
if is_pim_light and not os.environ.get("ANSYS_PLATFORM_INSTANCEMANAGEMENT_CONFIG", None):
961-
os.environ["ANSYS_PLATFORM_INSTANCEMANAGEMENT_CONFIG"] = DEFAULT_PIM_CONFIG
960+
os.environ["ANSYS_PLATFORM_INSTANCEMANAGEMENT_CONFIG"] = pygeom_defaults.DEFAULT_PIM_CONFIG
962961
pop_out = True
963962
else:
964963
pop_out = False
@@ -969,7 +968,7 @@ def _launch_pim_instance(
969968
instance.wait_for_ready()
970969
channel = instance.build_grpc_channel(
971970
options=[
972-
("grpc.max_receive_message_length", MAX_MESSAGE_LENGTH),
971+
("grpc.max_receive_message_length", pygeom_defaults.MAX_MESSAGE_LENGTH),
973972
]
974973
)
975974

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

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
BooleanRequest,
3737
CopyRequest,
3838
GetCollisionRequest,
39+
GetTessellationRequest,
3940
MapRequest,
4041
MirrorRequest,
4142
RotateRequest,
@@ -59,6 +60,7 @@
5960
ShellRequest,
6061
)
6162
from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub
63+
from ansys.api.geometry.v0.models_pb2 import TessellationOptions as GRPCTessellationOptions
6264
from ansys.geometry.core.connection.client import GrpcClient
6365
from ansys.geometry.core.connection.conversions import (
6466
frame_to_grpc_frame,
@@ -96,6 +98,7 @@
9698
min_backend_version,
9799
)
98100
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Angle, Distance
101+
from ansys.geometry.core.misc.options import TessellationOptions
99102
from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve
100103
from ansys.geometry.core.sketch.sketch import Sketch
101104
from ansys.geometry.core.typing import Real
@@ -564,7 +567,9 @@ def copy(self, parent: "Component", name: str = None) -> "Body":
564567
return
565568

566569
@abstractmethod
567-
def tessellate(self, merge: bool = False) -> Union["PolyData", "MultiBlock"]:
570+
def tessellate(
571+
self, merge: bool = False, tessellation_options: TessellationOptions = None
572+
) -> Union["PolyData", "MultiBlock"]:
568573
"""Tessellate the body and return the geometry as triangles.
569574
570575
Parameters
@@ -573,6 +578,8 @@ def tessellate(self, merge: bool = False) -> Union["PolyData", "MultiBlock"]:
573578
Whether to merge the body into a single mesh. When ``False`` (default), the
574579
number of triangles are preserved and only the topology is merged.
575580
When ``True``, the individual faces of the tessellation are merged.
581+
tessellation_options : TessellationOptions, default: None
582+
A set of options to determine the tessellation quality.
576583
577584
Returns
578585
-------
@@ -1266,7 +1273,10 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102
12661273
@protect_grpc
12671274
@graphics_required
12681275
def tessellate( # noqa: D102
1269-
self, merge: bool = False, transform: Matrix44 = IDENTITY_MATRIX44
1276+
self,
1277+
merge: bool = False,
1278+
transform: Matrix44 = IDENTITY_MATRIX44,
1279+
tess_options: TessellationOptions = None,
12701280
) -> Union["PolyData", "MultiBlock"]:
12711281
# lazy import here to improve initial module load time
12721282
import pyvista as pv
@@ -1278,11 +1288,45 @@ def tessellate( # noqa: D102
12781288

12791289
# cache tessellation
12801290
if not self._tessellation:
1281-
resp = self._bodies_stub.GetTessellation(self._grpc_id)
1282-
self._tessellation = {
1283-
str(face_id): tess_to_pd(face_tess)
1284-
for face_id, face_tess in resp.face_tessellation.items()
1285-
}
1291+
if tess_options is not None:
1292+
request = GetTessellationRequest(
1293+
id=self._grpc_id,
1294+
options=GRPCTessellationOptions(
1295+
surface_deviation=tess_options.surface_deviation,
1296+
angle_deviation=tess_options.angle_deviation,
1297+
maximum_aspect_ratio=tess_options.max_aspect_ratio,
1298+
maximum_edge_length=tess_options.max_edge_length,
1299+
watertight=tess_options.watertight,
1300+
),
1301+
)
1302+
try:
1303+
resp = self._bodies_stub.GetTessellationWithOptions(request)
1304+
self._tessellation = {
1305+
str(face_id): tess_to_pd(face_tess)
1306+
for face_id, face_tess in resp.face_tessellation.items()
1307+
}
1308+
except Exception:
1309+
tessellation_map = {}
1310+
for response in self._bodies_stub.GetTessellationStream(request):
1311+
for key, value in response.face_tessellation.items():
1312+
tessellation_map[key] = tess_to_pd(value)
1313+
1314+
self._tessellation = tessellation_map
1315+
else:
1316+
try:
1317+
resp = self._bodies_stub.GetTessellation(self._grpc_id)
1318+
self._tessellation = {
1319+
str(face_id): tess_to_pd(face_tess)
1320+
for face_id, face_tess in resp.face_tessellation.items()
1321+
}
1322+
except Exception:
1323+
tessellation_map = {}
1324+
request = GetTessellationRequest(self._grpc_id)
1325+
for response in self._bodies_stub.GetTessellationStream(request):
1326+
for key, value in response.face_tessellation.items():
1327+
tessellation_map[key] = tess_to_pd(value)
1328+
1329+
self._tessellation = tessellation_map
12861330

12871331
pdata = [tess.transform(transform, inplace=False) for tess in self._tessellation.values()]
12881332
comp = pv.MultiBlock(pdata)
@@ -1810,9 +1854,11 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102
18101854

18111855
@ensure_design_is_active
18121856
def tessellate( # noqa: D102
1813-
self, merge: bool = False
1857+
self, merge: bool = False, tess_options: TessellationOptions = None
18141858
) -> Union["PolyData", "MultiBlock"]:
1815-
return self._template.tessellate(merge, self.parent_component.get_world_transform())
1859+
return self._template.tessellate(
1860+
merge, self.parent_component.get_world_transform(), tess_options
1861+
)
18161862

18171863
@ensure_design_is_active
18181864
def shell_body(self, offset: Real) -> bool: # noqa: D102

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

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,7 @@ def download(
326326
file_location.write_bytes(received_bytes)
327327
self._grpc_client.log.debug(f"Design downloaded at location {file_location}.")
328328

329-
def __export_and_download_legacy(
330-
self,
331-
format: DesignFileFormat = DesignFileFormat.SCDOCX,
332-
) -> bytes:
329+
def __export_and_download_legacy(self, format: DesignFileFormat) -> bytes:
333330
"""Export and download the design from the server.
334331
335332
Notes
@@ -339,7 +336,7 @@ def __export_and_download_legacy(
339336
340337
Parameters
341338
----------
342-
format : DesignFileFormat, default: DesignFileFormat.SCDOCX
339+
format : DesignFileFormat
343340
Format for the file to save to.
344341
345342
Returns
@@ -371,15 +368,12 @@ def __export_and_download_legacy(
371368

372369
return received_bytes
373370

374-
def __export_and_download(
375-
self,
376-
format: DesignFileFormat = DesignFileFormat.SCDOCX,
377-
) -> bytes:
371+
def __export_and_download(self, format: DesignFileFormat) -> bytes:
378372
"""Export and download the design from the server.
379373
380374
Parameters
381375
----------
382-
format : DesignFileFormat, default: DesignFileFormat.SCDOCX
376+
format : DesignFileFormat
383377
Format for the file to save to.
384378
385379
Returns
@@ -402,10 +396,23 @@ def __export_and_download(
402396
DesignFileFormat.SCDOCX,
403397
DesignFileFormat.STRIDE,
404398
]:
405-
response = self._design_stub.DownloadExportFile(
406-
DownloadExportFileRequest(format=format.value[1])
407-
)
408-
received_bytes += response.data
399+
try:
400+
response = self._design_stub.DownloadExportFile(
401+
DownloadExportFileRequest(format=format.value[1])
402+
)
403+
received_bytes += response.data
404+
except Exception:
405+
self._grpc_client.log.warning(
406+
f"Failed to download the file in {format.value[0]} format."
407+
" Attempting to stream download."
408+
)
409+
# Attempt to download the file via streaming
410+
received_bytes = bytes()
411+
responses = self._design_stub.StreamDownloadExportFile(
412+
DownloadExportFileRequest(format=format.value[1])
413+
)
414+
for response in responses:
415+
received_bytes += response.data
409416
else:
410417
self._grpc_client.log.warning(
411418
f"{format.value[0]} format requested is not supported. Ignoring download request."

0 commit comments

Comments
 (0)