Skip to content

Commit

Permalink
feat: performance enhancements to plotter (#1496)
Browse files Browse the repository at this point in the history
Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com>
  • Loading branch information
RobPasMue and pyansys-ci-bot authored Oct 21, 2024
1 parent 39d412a commit b70e431
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 147 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ exclude: "tests/integration/files"
repos:

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.7.0
hooks:
- id: ruff
- id: ruff-format
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/1496.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
performance enhancements to plotter
Binary file removed doc/source/_static/assets/multicolors.png
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ the ``plot()`` method.

In the following cell we will create a new design and plot a prism and a cylinder in different colors.

```python
```{code-cell} ipython
design = modeler.create_design("MultiColors")

# Create a sketch of a box
Expand All @@ -193,8 +193,6 @@ design.extrude_sketch("Cylinder", sketch_circle, 50 * UNITS.m)
design.plot(multi_colors=True)
```

![](../../_static/assets/multicolors.png)

## Clip objects

You can clip any object represented in the plotter by defining a ``Plane`` object that
Expand Down
16 changes: 4 additions & 12 deletions doc/source/examples/03_modeling/tessellation_usage.mystnb
Original file line number Diff line number Diff line change
Expand Up @@ -76,26 +76,18 @@ combines all the faces of each individual body into a single dataset without
separating faces.

```{code-cell} ipython3
dataset = comp.tessellate(merge_bodies=True)
dataset = comp.tessellate()
dataset
```
If you want to tessellate the body and return the geometry as triangles, single body tessellation
is possible. If you want to merge the individual faces of the tessellation, enable the
``merge`` option so that the body is rendered into a single mesh. This preserves the number of
triangles and only merges the topology.

**Code without merging the body**
Single body tessellation is possible. In that case, users can request the body-level tessellation
method to tessellate the body and merge all the faces into a single dataset.

```{code-cell} ipython3
dataset = body.tessellate()
dataset = comp.bodies[0].tessellate()
dataset
```
**Code with merging the body**

```{code-cell} ipython3
mesh = body.tessellate(merge=True)
mesh
```
## Plot design

Plot the design.
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ all = [
tests = [
"ansys-platform-instancemanagement==1.1.2",
"ansys-tools-path==0.6.0",
"ansys-tools-visualization-interface==0.4.5",
"ansys-tools-visualization-interface==0.4.6",
"beartype==0.19.0",
"docker==7.1.0",
"grpcio==1.67.0",
Expand Down Expand Up @@ -77,7 +77,7 @@ tests-minimal = [
doc = [
"ansys-sphinx-theme[autoapi]==1.1.6",
"ansys-tools-path==0.6.0",
"ansys-tools-visualization-interface==0.4.5",
"ansys-tools-visualization-interface==0.4.6",
"beartype==0.19.0",
"docker==7.1.0",
"grpcio==1.67.0",
Expand Down
2 changes: 1 addition & 1 deletion src/ansys/geometry/core/connection/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ def tess_to_pd(tess: Tessellation) -> "PolyData":
import numpy as np
import pyvista as pv

return pv.PolyData(np.array(tess.vertices).reshape(-1, 3), tess.faces)
return pv.PolyData(var_inp=np.array(tess.vertices).reshape(-1, 3), faces=tess.faces)


def grpc_matrix_to_matrix(m: GRPCMatrix) -> Matrix44:
Expand Down
24 changes: 15 additions & 9 deletions src/ansys/geometry/core/designer/body.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def tessellate(self, merge: bool = False) -> Union["PolyData", "MultiBlock"]:
@abstractmethod
def plot(
self,
merge: bool = False,
merge: bool = True,
screenshot: str | None = None,
use_trame: bool | None = None,
use_service_colors: bool | None = None,
Expand All @@ -554,10 +554,11 @@ def plot(
Parameters
----------
merge : bool, default: False
Whether to merge the body into a single mesh. When ``False`` (default),
the number of triangles are preserved and only the topology is merged.
When ``True``, the individual faces of the tessellation are merged.
merge : bool, default: True
Whether to merge the body into a single mesh. Performance improved when ``True``.
When ``True`` (default), the individual faces of the tessellation are merged.
When ``False``, the number of triangles are preserved and only the topology
is merged.
screenshot : str, default: None
Path for saving a screenshot of the image that is being represented.
use_trame : bool, default: None
Expand Down Expand Up @@ -1118,14 +1119,16 @@ def tessellate( # noqa: D102

pdata = [tess_to_pd(tess).transform(transform) for tess in self._tessellation]
comp = pv.MultiBlock(pdata)

if merge:
ugrid = comp.combine()
return pv.PolyData(ugrid.points, ugrid.cells, n_faces=ugrid.n_cells)
return comp
return pv.PolyData(var_inp=ugrid.points, faces=ugrid.cells)
else:
return comp

def plot( # noqa: D102
self,
merge: bool = False,
merge: bool = True,
screenshot: str | None = None,
use_trame: bool | None = None,
use_service_colors: bool | None = None,
Expand Down Expand Up @@ -1510,7 +1513,7 @@ def tessellate( # noqa: D102

def plot( # noqa: D102
self,
merge: bool = False,
merge: bool = True,
screenshot: str | None = None,
use_trame: bool | None = None,
use_service_colors: bool | None = None,
Expand All @@ -1529,6 +1532,9 @@ def plot( # noqa: D102
else pyansys_geometry.USE_SERVICE_COLORS
)

# Add to plotting options as well... to be used by the plotter if necessary
plotting_options["merge_bodies"] = merge

mesh_object = (
self if use_service_colors else MeshObjectPlot(self, self.tessellate(merge=merge))
)
Expand Down
115 changes: 41 additions & 74 deletions src/ansys/geometry/core/designer/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -1350,85 +1350,47 @@ def _kill_component_on_client(self) -> None:
# Kill itself
self._is_alive = False

def tessellate(
self, merge_component: bool = False, merge_bodies: bool = False
) -> Union["PolyData", "MultiBlock"]:
def tessellate(self, _recursive_call: bool = False) -> Union["PolyData", list["MultiBlock"]]:
"""Tessellate the component.
Parameters
----------
merge_component : bool, default: False
Whether to merge this component into a single dataset. When ``True``,
all the individual bodies are effectively combined into a single
dataset without any hierarchy.
merge_bodies : bool, default: False
Whether to merge each body into a single dataset. When ``True``,
all the faces of each individual body are effectively
merged into a single dataset without separating faces.
_recursive_call: bool, default: False
Internal flag to indicate if this method is being called recursively.
Not to be used by the user.
Returns
-------
~pyvista.PolyData, ~pyvista.MultiBlock
Merged :class:`pyvista.PolyData` if ``merge_component=True`` or a
composite dataset.
~pyvista.PolyData, list[~pyvista.MultiBlock]
Tessellated component as a single PolyData object.
If the method is called recursively, a list of MultiBlock objects is returned.
Examples
--------
Create two stacked bodies and return the tessellation as two merged bodies:
>>> from ansys.geometry.core.sketch import Sketch
>>> from ansys.geometry.core import Modeler
>>> from ansys.geometry.core.math import Point2D, Point3D, Plane
>>> from ansys.geometry.core.misc import UNITS
>>> modeler = Modeler("10.54.0.72", "50051")
>>> sketch_1 = Sketch()
>>> box = sketch_1.box(
>>> Point2D([10, 10], UNITS.m), Quantity(10, UNITS.m), Quantity(5, UNITS.m))
>>> sketch_1.circle(Point2D([0, 0], UNITS.m), Quantity(25, UNITS.m))
>>> design = modeler.create_design("MyDesign")
>>> comp = design.add_component("MyComponent")
>>> distance = Quantity(10, UNITS.m)
>>> body = comp.extrude_sketch("Body", sketch=sketch_1, distance=distance)
>>> sketch_2 = Sketch(Plane([0, 0, 10]))
>>> box = sketch_2.box(
>>> Point2D([10, 10], UNITS.m), Quantity(10, UNITS.m), Quantity(5, UNITS.m))
>>> circle = sketch_2.circle(Point2D([0, 0], UNITS.m), Quantity(25, UNITS.m))
>>> body = comp.extrude_sketch("Body", sketch=sketch_2, distance=distance)
>>> dataset = comp.tessellate(merge_bodies=True)
>>> dataset
MultiBlock (0x7ff6bcb511e0)
N Blocks: 2
X Bounds: -25.000, 25.000
Y Bounds: -24.991, 24.991
Z Bounds: 0.000, 20.000
"""
import pyvista as pv

# Tessellate the bodies in this component
datasets = [body.tessellate(merge_bodies) for body in self.bodies]

blocks_list = [pv.MultiBlock(datasets)]
datasets: list["MultiBlock"] = [body.tessellate(merge=False) for body in self.bodies]

# Now, go recursively inside its subcomponents (with no arguments) and
# merge the PolyData obtained into our blocks
for comp in self._components:
if not comp.is_alive:
continue
blocks_list.append(comp.tessellate(merge_bodies=merge_bodies))

# Transform the list of MultiBlock objects into a single MultiBlock
blocks = pv.MultiBlock(blocks_list)
datasets.extend(comp.tessellate(_recursive_call=True))

if merge_component:
ugrid = blocks.combine()
# Convert to polydata as it's slightly faster than extract surface
return pv.PolyData(ugrid.points, ugrid.cells, n_faces=ugrid.n_cells)
return blocks
# Convert to polydata as it's slightly faster than extract surface
# plus this method is only for visualizing the component as a whole (no
# need to keep the hierarchy)
if _recursive_call:
return datasets
else:
ugrid = pv.MultiBlock(datasets).combine()
return pv.PolyData(var_inp=ugrid.points, faces=ugrid.cells)

def plot(
self,
merge_component: bool = False,
merge_bodies: bool = False,
merge_component: bool = True,
merge_bodies: bool = True,
screenshot: str | None = None,
use_trame: bool | None = None,
use_service_colors: bool | None = None,
Expand All @@ -1438,14 +1400,15 @@ def plot(
Parameters
----------
merge_component : bool, default: False
Whether to merge the component into a single dataset. When ``True``,
all the individual bodies are effectively merged into a single
dataset without any hierarchy.
merge_bodies : bool, default: False
Whether to merge each body into a single dataset. When ``True``,
all the faces of each individual body are effectively merged
into a single dataset without separating faces.
merge_component : bool, default: True
Whether to merge the component into a single dataset. By default, ``True``.
Performance improved. When ``True``, all the faces of the component are effectively
merged into a single dataset. If ``False``, the individual bodies are kept separate.
merge_bodies : bool, default: True
Whether to merge each body into a single dataset. By default, ``True``.
Performance improved. When ``True``, all the faces of each individual body are
effectively merged into a single dataset. If ``False``, the individual faces are kept
separate.
screenshot : str, default: None
Path for saving a screenshot of the image being represented.
use_trame : bool, default: None
Expand Down Expand Up @@ -1496,24 +1459,28 @@ def plot(
"""
import ansys.geometry.core as pyansys_geometry
from ansys.geometry.core.plotting import GeometryPlotter
from ansys.tools.visualization_interface.types.mesh_object_plot import MeshObjectPlot

use_service_colors = (
use_service_colors
if use_service_colors is not None
else pyansys_geometry.USE_SERVICE_COLORS
)

mesh_object = (
self
if use_service_colors
else MeshObjectPlot(
custom_object=self,
mesh=self.tessellate(merge_component=merge_component, merge_bodies=merge_bodies),
# Add merge_component and merge_bodies to the plotting options
plotting_options["merge_component"] = merge_component
plotting_options["merge_bodies"] = merge_bodies

# At component level, if ``multi_colors`` or ``use_service_colors`` are defined
# we should not merge the component.
if plotting_options.get("multi_colors", False) or use_service_colors:
plotting_options["merge_component"] = False
self._grpc_client.log.info(
"Ignoring 'merge_component=True' (default behavior) as "
"'multi_colors' or 'use_service_colors' are defined."
)
)

pl = GeometryPlotter(use_trame=use_trame, use_service_colors=use_service_colors)
pl.plot(mesh_object, **plotting_options)
pl.plot(self, **plotting_options)
pl.show(screenshot=screenshot, **plotting_options)

def __repr__(self) -> str:
Expand Down
Loading

0 comments on commit b70e431

Please sign in to comment.