Skip to content

Commit 13e2c45

Browse files
raw tessellation methods for body/design in place
1 parent 36f50c7 commit 13e2c45

File tree

4 files changed

+140
-32
lines changed

4 files changed

+140
-32
lines changed

src/ansys/geometry/core/_grpc/_services/v0/designs.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
_check_write_body_facets_input,
3131
build_grpc_id,
3232
from_design_file_format_to_grpc_part_export_format,
33+
from_grpc_tess_to_pd,
34+
from_tess_options_to_grpc_tess_options,
3335
)
3436

3537

@@ -261,27 +263,30 @@ def request_generator(
261263
def stream_design_tessellation(self, **kwargs) -> dict: # noqa: D102
262264
from ansys.api.dbu.v0.designs_pb2 import DesignTessellationRequest
263265

266+
# If there are options, convert to gRPC options
267+
options = (
268+
from_tess_options_to_grpc_tess_options(kwargs["options"])
269+
if kwargs["options"] is not None
270+
else None
271+
)
272+
264273
# Create the request - assumes all inputs are valid and of the proper type
265274
request = DesignTessellationRequest(
266-
chordal_deviation=kwargs["chordal_deviation"],
267-
angle_deviation=kwargs["angle_deviation"],
268-
max_aspect_ratio=kwargs["max_aspect_ratio"],
269-
min_edge_length=kwargs["min_edge_length"],
270-
max_edge_length=kwargs["max_edge_length"],
271-
deflection_type=kwargs["deflection_type"],
275+
options=options
272276
)
273277

274278
# Call the gRPC service
275279
response = self.designs_stub.StreamDesignTessellation(request)
276280

277281
# Return the response - formatted as a dictionary
278-
points = []
279-
triangles = []
282+
tess_map = {}
280283
for elem in response:
281-
points.extend(elem.points)
282-
triangles.extend(elem.triangles)
284+
for body_id, body_tess in elem.body_tessellation.items():
285+
tess = {}
286+
for face_id, face_tess in body_tess.face_tessellation.items():
287+
tess[face_id] = from_grpc_tess_to_pd(face_tess)
288+
tess_map[body_id] = tess
283289

284290
return {
285-
"points": points,
286-
"triangles": triangles,
291+
"tessellation": tess_map,
287292
}

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,21 @@ def copy(self, parent: "Component", name: str = None) -> "Body":
594594
"""
595595
return
596596

597+
@abstractmethod
598+
def get_raw_tessellation(self, tess_options: TessellationOptions | None = None) -> dict:
599+
"""Tessellate the body and return the raw tessellation data.
600+
601+
Parameters
602+
----------
603+
tess_options : TessellationOptions | None, default: None
604+
A set of options to determine the tessellation quality.
605+
606+
Returns
607+
-------
608+
dict
609+
Dictionary with face IDs as keys and :class:`pyvista.PolyData` as values.
610+
"""
611+
597612
@abstractmethod
598613
def tessellate(
599614
self, merge: bool = False, tess_options: TessellationOptions | None = None
@@ -1281,6 +1296,38 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102
12811296
body_id = f"{parent.id}/{tb.id}" if parent.parent_component else tb.id
12821297
return Body(body_id, response.get("name"), parent, tb)
12831298

1299+
@graphics_required
1300+
def get_raw_tessellation(self, tess_options: TessellationOptions | None = None) -> dict: # noqa: D102
1301+
if not self.is_alive:
1302+
return {}
1303+
1304+
# If the server does not support tessellation options, ignore them
1305+
if tess_options is not None and self._grpc_client.backend_version < (25, 2, 0):
1306+
self._grpc_client.log.warning(
1307+
"Tessellation options are not supported by server"
1308+
f" version {self._grpc_client.backend_version}. Ignoring options."
1309+
)
1310+
tess_options = None
1311+
1312+
self._grpc_client.log.debug(f"Requesting tessellation for body {self.id}.")
1313+
1314+
# cache tessellation
1315+
if not self._tessellation:
1316+
if tess_options is not None:
1317+
response = self._grpc_client.services.bodies.get_tesellation_with_options(
1318+
id=self.id,
1319+
options=tess_options,
1320+
)
1321+
else:
1322+
response = self._grpc_client.services.bodies.get_tesellation(
1323+
id=self.id,
1324+
backend_version=self._grpc_client.backend_version,
1325+
)
1326+
1327+
self._tessellation = response.get("tessellation")
1328+
1329+
return self._tessellation
1330+
12841331
@graphics_required
12851332
def tessellate( # noqa: D102
12861333
self,
@@ -1849,6 +1896,10 @@ def get_collision(self, body: "Body") -> CollisionType: # noqa: D102
18491896
def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102
18501897
return self._template.copy(parent, name)
18511898

1899+
@ensure_design_is_active
1900+
def get_raw_tessellation(self, tess_options: TessellationOptions | None = None) -> dict: # noqa: D102
1901+
return self._template.get_raw_tessellation(tess_options)
1902+
18521903
@ensure_design_is_active
18531904
def tessellate( # noqa: D102
18541905
self, merge: bool = False, tess_options: TessellationOptions | None = None

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

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,30 +1040,25 @@ def insert_file(
10401040
@min_backend_version(26, 1, 0)
10411041
@check_input_types
10421042
@graphics_required
1043-
def tessellate(
1044-
self, merge: bool = False, tess_options: TessellationOptions | None = None
1045-
) -> Union["PolyData", "MultiBlock"]:
1043+
def tessellate(self, tess_options: TessellationOptions | None = None) -> dict:
10461044
"""Tessellate the entire design and return the geometry as triangles.
10471045
10481046
Parameters
10491047
----------
1050-
merge : bool, default: False
1051-
Whether to merge all bodies into a single mesh.
10521048
tess_options : TessellationOptions, optional
10531049
Options for the tessellation. If None, default options are used.
10541050
10551051
Returns
10561052
-------
1057-
~pyvista.PolyData | ~pyvista.MultiBlock
1058-
The tessellated mesh. If `merge` is True, a single PolyData is returned.
1059-
Otherwise, a MultiBlock is returned with each block corresponding to a body.
1053+
dict
1054+
A dictionary with body IDs as keys and another dictionary as values.
1055+
The inner dictionary has face IDs as keys and the corresponding PyVista
1056+
PolyData objects as values.
10601057
"""
1061-
import pyvista as pv
1062-
10631058
if not self.is_alive:
1064-
return pv.PolyData() if merge else pv.MultiBlock()
1059+
return {} # Return an empty dictionary if the design is not alive
10651060

1066-
self._grpc_client.log.debug(f"Requesting tessellation for body {self.id}.")
1061+
self._grpc_client.log.debug(f"Requesting tessellation for design {self.id}.")
10671062

10681063
# cache tessellation
10691064
if not self._tessellation:
@@ -1073,14 +1068,7 @@ def tessellate(
10731068

10741069
self._tessellation = response.get("tessellation")
10751070

1076-
pdata = [tess for tess in self._tessellation.values()]
1077-
comp = pv.MultiBlock(pdata)
1078-
1079-
if merge:
1080-
ugrid = comp.combine()
1081-
return pv.PolyData(var_inp=ugrid.points, faces=ugrid.cells)
1082-
else:
1083-
return comp
1071+
return self._tessellation
10841072

10851073
def __repr__(self) -> str:
10861074
"""Represent the ``Design`` as a string."""

tests/integration/test_tessellation.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,67 @@ def test_component_tessellate(modeler: Modeler):
161161
rel=1e-6,
162162
abs=1e-8,
163163
)
164+
165+
166+
def test_get_design_tessellation(modeler: Modeler):
167+
"""Test getting the entire design tessellation."""
168+
import pyvista as pv
169+
170+
design = modeler.create_design("revolve_edges")
171+
box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 2, 2), 2)
172+
cyl = design.extrude_sketch("cylinder", Sketch().circle(Point2D([1, 0]), 0.5), 2)
173+
174+
design_tess = design.tessellate()
175+
assert isinstance(design_tess, dict)
176+
assert len(design_tess) == 2 # Two bodies in the design
177+
178+
box_tess = design_tess[box.id]
179+
assert isinstance(box_tess, dict)
180+
assert len(box_tess) == 6 # Six faces on the box
181+
182+
for face_id, face_tess in box_tess.items():
183+
assert isinstance(face_id, str)
184+
assert isinstance(face_tess, pv.PolyData)
185+
assert face_tess.n_cells == 2
186+
assert face_tess.n_points == 4
187+
188+
cyl_tess = design_tess[cyl.id]
189+
assert isinstance(cyl_tess, dict)
190+
assert len(cyl_tess) == 3 # Three faces on the cylinder
191+
192+
for face_id, face_tess in cyl_tess.items():
193+
assert isinstance(face_id, str)
194+
assert isinstance(face_tess, pv.PolyData)
195+
assert face_tess.n_cells > 0
196+
assert face_tess.n_points > 0
197+
198+
199+
def test_get_body_raw_tessellation(modeler: Modeler):
200+
"""Test getting the raw tessellation from a body."""
201+
import pyvista as pv
202+
203+
design = modeler.create_design("revolve_edges")
204+
box = design.extrude_sketch("box", Sketch().box(Point2D([0, 0]), 2, 2), 2)
205+
cylinder = design.extrude_sketch("cylinder", Sketch().circle(Point2D([1, 0]), 0.5), 2)
206+
207+
# Get the raw tessellation from the box body
208+
box_tess = box.get_raw_tessellation()
209+
assert isinstance(box_tess, dict)
210+
assert len(box_tess) == 6 # Six faces on the box
211+
212+
for face_id, face_tess in box_tess.items():
213+
assert isinstance(face_id, str)
214+
assert isinstance(face_tess, pv.PolyData)
215+
assert face_tess.n_cells == 2
216+
assert face_tess.n_points == 4
217+
218+
# Get the raw tessellation from the cylinder body
219+
cyl_tess = cylinder.get_raw_tessellation()
220+
assert isinstance(cyl_tess, dict)
221+
assert len(cyl_tess) == 3 # Three faces on the cylinder
222+
223+
for face_id, face_tess in cyl_tess.items():
224+
assert isinstance(face_id, str)
225+
assert isinstance(face_tess, pv.PolyData)
226+
assert face_tess.n_cells > 0
227+
assert face_tess.n_points > 0

0 commit comments

Comments
 (0)