From 0ed5db89100e0efcc51e5a7ef1230f0b4432be4b Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 6 Feb 2026 13:24:23 +0100 Subject: [PATCH 01/31] feat: Add customization APIs --- examples/00-basic-pyvista-examples/README.txt | 14 +- .../customization_api.py | 113 ++++++++ .../customization_api.py | 124 +++++++++ .../visualization_interface/backends/_base.py | 128 ++++++++- .../backends/plotly/plotly_interface.py | 244 ++++++++++++++++++ .../backends/pyvista/pyvista.py | 225 ++++++++++++++++ .../tools/visualization_interface/plotter.py | 231 ++++++++++++++++- 7 files changed, 1076 insertions(+), 3 deletions(-) create mode 100644 examples/00-basic-pyvista-examples/customization_api.py create mode 100644 examples/01-basic-plotly-examples/customization_api.py diff --git a/examples/00-basic-pyvista-examples/README.txt b/examples/00-basic-pyvista-examples/README.txt index 568ba6455..f89d7b0bc 100644 --- a/examples/00-basic-pyvista-examples/README.txt +++ b/examples/00-basic-pyvista-examples/README.txt @@ -1,4 +1,16 @@ Basic usage examples ==================== -These examples show how to use the general plotter included in the Visualization Interface Tool. \ No newline at end of file +These examples show how to use the general plotter included in the Visualization Interface Tool. + +New: Customization APIs +------------------------ + +See ``customization_api.py`` for examples of the new backend-agnostic customization APIs: + +- ``add_points()`` - Add point markers +- ``add_lines()`` - Add line segments +- ``add_planes()`` - Add reference planes +- ``add_text()`` - Add text labels + +These APIs work consistently across different backends. \ No newline at end of file diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py new file mode 100644 index 000000000..2154ff6ab --- /dev/null +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -0,0 +1,113 @@ +# Copyright (C) 2024 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _customization_api_example: + +Backend-Agnostic Customization APIs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example demonstrates the new backend-agnostic customization APIs +that allow you to add points, lines, planes, and text to visualizations +without directly coupling to the PyVista backend. + +These APIs work consistently across different backends, making your code +more portable and maintainable. +""" + +import pyvista as pv +from ansys.tools.visualization_interface import Plotter + +############################################################################### +# Create a plotter and add basic geometry +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# First, create a plotter and add some geometry to visualize. + +plotter = Plotter() + +# Add a sphere as our main geometry +sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) +plotter.plot(sphere, color='lightblue', opacity=0.6) + +############################################################################### +# Add points +# ~~~~~~~~~~ +# Add point markers to highlight specific locations. + +key_points = [ + [1, 0, 0], # Point on X axis + [0, 1, 0], # Point on Y axis + [0, 0, 1], # Point on Z axis +] + +plotter.add_points(key_points, color='red', size=20) + +############################################################################### +# Add lines +# ~~~~~~~~~ +# Add line segments to show coordinate axes or connections. + +# X axis +x_axis = [[0, 0, 0], [1.5, 0, 0]] +plotter.add_lines(x_axis, color='red', width=4.0) + +# Y axis +y_axis = [[0, 0, 0], [0, 1.5, 0]] +plotter.add_lines(y_axis, color='green', width=4.0) + +# Z axis +z_axis = [[0, 0, 0], [0, 0, 1.5]] +plotter.add_lines(z_axis, color='blue', width=4.0) + +############################################################################### +# Add a reference plane +# ~~~~~~~~~~~~~~~~~~~~~ +# Add a plane to show a reference surface. + +plotter.add_planes( + center=(0, 0, 0), + normal=(0, 0, 1), + i_size=2.5, + j_size=2.5, + color='white', + opacity=0.2 +) + +############################################################################### +# Add text labels +# ~~~~~~~~~~~~~~~ +# Add text annotations to label features. + +# Axis labels (3D world coordinates) +plotter.add_text("X", position=(1.6, 0, 0), font_size=14, color='red') +plotter.add_text("Y", position=(0, 1.6, 0), font_size=14, color='green') +plotter.add_text("Z", position=(0, 0, 1.6), font_size=14, color='blue') + +# Title (2D screen coordinates) +plotter.add_text("Customization API Example", position=(10, 10), font_size=18, color='white') + +############################################################################### +# Show the result +# ~~~~~~~~~~~~~~~ +# Display the visualization with all customizations. + +plotter.show() diff --git a/examples/01-basic-plotly-examples/customization_api.py b/examples/01-basic-plotly-examples/customization_api.py new file mode 100644 index 000000000..d0c9a5fc5 --- /dev/null +++ b/examples/01-basic-plotly-examples/customization_api.py @@ -0,0 +1,124 @@ +# Copyright (C) 2024 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _plotly_customization_api_example: + +Backend-Agnostic Customization APIs (Plotly) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example demonstrates the backend-agnostic customization APIs with +the Plotly backend. + +The same API calls work with both PyVista and Plotly backends, demonstrating +the true backend-agnostic nature of these methods. +""" + +import pyvista as pv +from ansys.tools.visualization_interface import Plotter +from ansys.tools.visualization_interface.backends.plotly.plotly_interface import PlotlyBackend + +############################################################################### +# Create a plotter with Plotly backend +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a plotter using the Plotly backend and add basic geometry. + +plotter = Plotter(backend=PlotlyBackend()) + +# Add a sphere - this works fine +sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) +plotter.plot(sphere) + +print("✓ Basic geometry added successfully with Plotly backend") + +############################################################################### +# Add points +# ~~~~~~~~~~ +# Add point markers to highlight specific locations. + +key_points = [ + [1, 0, 0], # Point on X axis + [0, 1, 0], # Point on Y axis + [0, 0, 1], # Point on Z axis +] + +plotter.add_points(key_points, color='red', size=10) +print("✓ Points added successfully") + +############################################################################### +# Add lines +# ~~~~~~~~~ +# Add line segments to show coordinate axes. + +# X axis +x_axis = [[0, 0, 0], [1.5, 0, 0]] +plotter.add_lines(x_axis, color='red', width=4.0) + +# Y axis +y_axis = [[0, 0, 0], [0, 1.5, 0]] +plotter.add_lines(y_axis, color='green', width=4.0) + +# Z axis +z_axis = [[0, 0, 0], [0, 0, 1.5]] +plotter.add_lines(z_axis, color='blue', width=4.0) + +print("✓ Lines added successfully") + +############################################################################### +# Add a reference plane +# ~~~~~~~~~~~~~~~~~~~~~ +# Add a plane to show a reference surface. + +plotter.add_planes( + center=(0, 0, 0), + normal=(0, 0, 1), + i_size=2.5, + j_size=2.5, + color='lightblue', + opacity=0.2 +) +print("✓ Plane added successfully") + +############################################################################### +# Add text labels +# ~~~~~~~~~~~~~~~ +# Add text annotations to label features. + +# Axis labels (3D world coordinates) +plotter.add_text("X", position=(1.6, 0, 0), font_size=14, color='red') +plotter.add_text("Y", position=(0, 1.6, 0), font_size=14, color='green') +plotter.add_text("Z", position=(0, 0, 1.6), font_size=14, color='blue') +print("✓ Text labels added successfully") + +############################################################################### +# Show the result +# ~~~~~~~~~~~~~~~ +# Display the visualization with all customizations. + +print("\n" + "="*70) +print("Summary:") +print(" ✓ All customization APIs work with Plotly backend!") +print(" ✓ Same API calls as PyVista - truly backend-agnostic") +print("="*70) + +# Uncomment to show in browser: +plotter.show() diff --git a/src/ansys/tools/visualization_interface/backends/_base.py b/src/ansys/tools/visualization_interface/backends/_base.py index c7d5f6735..017e09e75 100644 --- a/src/ansys/tools/visualization_interface/backends/_base.py +++ b/src/ansys/tools/visualization_interface/backends/_base.py @@ -22,7 +22,7 @@ """Module for the backend base class.""" from abc import ABC, abstractmethod -from typing import Any, Iterable +from typing import Any, Iterable, List, Optional, Tuple, Union class BaseBackend(ABC): @@ -42,3 +42,129 @@ def plot_iter(self, plotting_list: Iterable): def show(self): """Show the plotted objects.""" raise NotImplementedError("show method must be implemented") + + @abstractmethod + def add_points( + self, + points: Union[List, Any], + color: str = "red", + size: float = 10.0, + **kwargs + ) -> Any: + """Add point markers to the scene. + + Parameters + ---------- + points : Union[List, Any] + Points to add. Can be a list of coordinates or array-like object. + Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + color : str, default: "red" + Color of the points. + size : float, default: 10.0 + Size of the point markers. + **kwargs : dict + Additional backend-specific keyword arguments. + + Returns + ------- + Any + Backend-specific actor or object representing the added points. + """ + raise NotImplementedError("add_points method must be implemented") + + @abstractmethod + def add_lines( + self, + points: Union[List, Any], + connections: Optional[Union[List, Any]] = None, + color: str = "white", + width: float = 1.0, + **kwargs + ) -> Any: + """Add line segments to the scene. + + Parameters + ---------- + points : Union[List, Any] + Points defining the lines. Can be a list of coordinates or array-like object. + Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + connections : Optional[Union[List, Any]], default: None + Line connectivity. If None, connects points sequentially. + Expected format: [[start_idx1, end_idx1], [start_idx2, end_idx2], ...] + or Mx2 array where M is the number of lines. + color : str, default: "white" + Color of the lines. + width : float, default: 1.0 + Width of the lines. + **kwargs : dict + Additional backend-specific keyword arguments. + + Returns + ------- + Any + Backend-specific actor or object representing the added lines. + """ + raise NotImplementedError("add_lines method must be implemented") + + @abstractmethod + def add_planes( + self, + center: Tuple[float, float, float] = (0.0, 0.0, 0.0), + normal: Tuple[float, float, float] = (0.0, 0.0, 1.0), + i_size: float = 1.0, + j_size: float = 1.0, + **kwargs + ) -> Any: + """Add a plane to the scene. + + Parameters + ---------- + center : Tuple[float, float, float], default: (0.0, 0.0, 0.0) + Center point of the plane (x, y, z). + normal : Tuple[float, float, float], default: (0.0, 0.0, 1.0) + Normal vector of the plane (x, y, z). + i_size : float, default: 1.0 + Size of the plane in the i direction. + j_size : float, default: 1.0 + Size of the plane in the j direction. + **kwargs : dict + Additional backend-specific keyword arguments. + + Returns + ------- + Any + Backend-specific actor or object representing the added plane. + """ + raise NotImplementedError("add_planes method must be implemented") + + @abstractmethod + def add_text( + self, + text: str, + position: Union[Tuple[float, float], Tuple[float, float, float]], + font_size: int = 12, + color: str = "white", + **kwargs + ) -> Any: + """Add text to the scene. + + Parameters + ---------- + text : str + Text string to display. + position : Union[Tuple[float, float], Tuple[float, float, float]] + Position for the text. Can be 2D (x, y) for screen coordinates + or 3D (x, y, z) for world coordinates. + font_size : int, default: 12 + Font size for the text. + color : str, default: "white" + Color of the text. + **kwargs : dict + Additional backend-specific keyword arguments. + + Returns + ------- + Any + Backend-specific actor or object representing the added text. + """ + raise NotImplementedError("add_text method must be implemented") diff --git a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py index 730f65b54..aed20256a 100644 --- a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py +++ b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py @@ -240,3 +240,247 @@ def show(self, self._fig.write_html(screenshot_str) else: self._fig.write_image(screenshot_str) + + def add_points(self, points, color="red", size=10.0, **kwargs): + """Add point markers to the scene. + + Parameters + ---------- + points : Union[List, Any] + Points to add. Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + color : str, default: "red" + Color of the points. + size : float, default: 10.0 + Size of the point markers. + **kwargs : dict + Additional keyword arguments passed to Plotly's Scatter3d. + + Returns + ------- + go.Scatter3d + Plotly Scatter3d trace representing the added points. + """ + import numpy as np + + # Convert points to numpy array + points_array = np.asarray(points) + if points_array.ndim == 1: + points_array = points_array.reshape(-1, 3) + + # Create Plotly scatter trace for points + scatter = go.Scatter3d( + x=points_array[:, 0], + y=points_array[:, 1], + z=points_array[:, 2], + mode='markers', + marker=dict( + size=size, + color=color, + ), + **kwargs + ) + + self._fig.add_trace(scatter) + return scatter + + def add_lines(self, points, connections=None, color="white", width=1.0, **kwargs): + """Add line segments to the scene. + + Parameters + ---------- + points : Union[List, Any] + Points defining the lines. Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + connections : Optional[Union[List, Any]], default: None + Line connectivity. If None, connects points sequentially. + Expected format: [[start_idx1, end_idx1], [start_idx2, end_idx2], ...] or Mx2 array. + color : str, default: "white" + Color of the lines. + width : float, default: 1.0 + Width of the lines. + **kwargs : dict + Additional keyword arguments passed to Plotly's Scatter3d. + + Returns + ------- + Union[go.Scatter3d, List[go.Scatter3d]] + Plotly Scatter3d trace(s) representing the added lines. + """ + import numpy as np + + # Convert points to numpy array + points_array = np.asarray(points) + if points_array.ndim == 1: + points_array = points_array.reshape(-1, 3) + + # Create connectivity if not provided (sequential connections) + if connections is None: + n_points = len(points_array) + if n_points < 2: + raise ValueError("At least 2 points are required to create lines") + connections_array = np.array([[i, i + 1] for i in range(n_points - 1)]) + else: + connections_array = np.asarray(connections) + if connections_array.ndim == 1: + connections_array = connections_array.reshape(-1, 2) + + # For Plotly, we need to create separate line traces or use None to break lines + # We'll create line coordinates with None separators for disconnected segments + x_coords = [] + y_coords = [] + z_coords = [] + + for conn in connections_array: + x_coords.extend([points_array[conn[0], 0], points_array[conn[1], 0], None]) + y_coords.extend([points_array[conn[0], 1], points_array[conn[1], 1], None]) + z_coords.extend([points_array[conn[0], 2], points_array[conn[1], 2], None]) + + # Create Plotly scatter trace for lines + line_trace = go.Scatter3d( + x=x_coords, + y=y_coords, + z=z_coords, + mode='lines', + line=dict( + color=color, + width=width, + ), + **kwargs + ) + + self._fig.add_trace(line_trace) + return line_trace + + def add_planes(self, center=(0.0, 0.0, 0.0), normal=(0.0, 0.0, 1.0), i_size=1.0, j_size=1.0, **kwargs): + """Add a plane to the scene. + + Parameters + ---------- + center : Tuple[float, float, float], default: (0.0, 0.0, 0.0) + Center point of the plane (x, y, z). + normal : Tuple[float, float, float], default: (0.0, 0.0, 1.0) + Normal vector of the plane (x, y, z). + i_size : float, default: 1.0 + Size of the plane in the i direction. + j_size : float, default: 1.0 + Size of the plane in the j direction. + **kwargs : dict + Additional keyword arguments passed to Plotly's Mesh3d (e.g., color, opacity). + + Returns + ------- + go.Mesh3d + Plotly Mesh3d trace representing the added plane. + """ + import numpy as np + + # Normalize the normal vector + normal_array = np.array(normal) + normal_array = normal_array / np.linalg.norm(normal_array) + + # Create two perpendicular vectors to the normal for the plane + # Choose an arbitrary vector not parallel to normal + if abs(normal_array[0]) < 0.9: + v1 = np.cross(normal_array, [1, 0, 0]) + else: + v1 = np.cross(normal_array, [0, 1, 0]) + v1 = v1 / np.linalg.norm(v1) * i_size / 2 + + v2 = np.cross(normal_array, v1) + v2 = v2 / np.linalg.norm(v2) * j_size / 2 + + # Create plane corners + center_array = np.array(center) + corners = [ + center_array - v1 - v2, + center_array + v1 - v2, + center_array + v1 + v2, + center_array - v1 + v2, + ] + + # Extract coordinates + x = [c[0] for c in corners] + y = [c[1] for c in corners] + z = [c[2] for c in corners] + + # Create two triangles to form the plane + # Triangle indices: 0-1-2 and 0-2-3 + i_indices = [0, 0] + j_indices = [1, 2] + k_indices = [2, 3] + + # Set default styling if not provided + if 'color' not in kwargs: + kwargs['color'] = 'lightblue' + if 'opacity' not in kwargs: + kwargs['opacity'] = 0.5 + + # Create Plotly mesh trace for plane + plane_trace = go.Mesh3d( + x=x, + y=y, + z=z, + i=i_indices, + j=j_indices, + k=k_indices, + **kwargs + ) + + self._fig.add_trace(plane_trace) + return plane_trace + + def add_text(self, text, position, font_size=12, color="white", **kwargs): + """Add text to the scene. + + Parameters + ---------- + text : str + Text string to display. + position : Union[Tuple[float, float], Tuple[float, float, float]] + Position for the text. For 3D, provide (x, y, z) coordinates. + For 2D annotations, this will be interpreted as 3D coordinates. + font_size : int, default: 12 + Font size for the text. + color : str, default: "white" + Color of the text. + **kwargs : dict + Additional keyword arguments passed to Plotly's Scatter3d or annotation. + + Returns + ------- + Union[go.Scatter3d, dict] + Plotly trace or annotation representing the added text. + """ + # For Plotly, we'll use Scatter3d with text mode for 3D text + if len(position) == 3: + # 3D text using scatter points with text + text_trace = go.Scatter3d( + x=[position[0]], + y=[position[1]], + z=[position[2]], + mode='text', + text=[text], + textfont=dict( + size=font_size, + color=color, + ), + **kwargs + ) + self._fig.add_trace(text_trace) + return text_trace + else: + # 2D annotation + annotation = dict( + x=position[0] if len(position) > 0 else 0, + y=position[1] if len(position) > 1 else 0, + text=text, + font=dict( + size=font_size, + color=color, + ), + showarrow=False, + xref="paper", + yref="paper", + **kwargs + ) + self._fig.add_annotation(annotation) + return annotation diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py index f6081e52b..8459290f4 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py @@ -737,3 +737,228 @@ def create_animation( ) return animation + + def add_points( + self, + points: Union[List, Any], + color: str = "red", + size: float = 10.0, + **kwargs + ) -> "pv.Actor": + """Add point markers to the scene. + + Parameters + ---------- + points : Union[List, Any] + Points to add. Can be a list of coordinates or array-like object. + Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + color : str, default: "red" + Color of the points. + size : float, default: 10.0 + Size of the point markers. + **kwargs : dict + Additional keyword arguments passed to PyVista's add_mesh method. + + Returns + ------- + pv.Actor + PyVista actor representing the added points. + """ + import numpy as np + + # Convert points to numpy array if needed + points_array = np.asarray(points) + + # Ensure points are 2D with shape (N, 3) + if points_array.ndim == 1: + points_array = points_array.reshape(-1, 3) + + # Create PyVista PolyData from points + point_cloud = pv.PolyData(points_array) + + # Add points to the scene + actor = self._pl.scene.add_mesh( + point_cloud, + color=color, + point_size=size, + render_points_as_spheres=True, + **kwargs + ) + + return actor + + def add_lines( + self, + points: Union[List, Any], + connections: Optional[Union[List, Any]] = None, + color: str = "white", + width: float = 1.0, + **kwargs + ) -> "pv.Actor": + """Add line segments to the scene. + + Parameters + ---------- + points : Union[List, Any] + Points defining the lines. Can be a list of coordinates or array-like object. + Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + connections : Optional[Union[List, Any]], default: None + Line connectivity. If None, connects points sequentially. + Expected format: [[start_idx1, end_idx1], [start_idx2, end_idx2], ...] + or Mx2 array where M is the number of lines. + color : str, default: "white" + Color of the lines. + width : float, default: 1.0 + Width of the lines. + **kwargs : dict + Additional keyword arguments passed to PyVista's add_mesh method. + + Returns + ------- + pv.Actor + PyVista actor representing the added lines. + """ + import numpy as np + + # Convert points to numpy array + points_array = np.asarray(points) + + # Ensure points are 2D with shape (N, 3) + if points_array.ndim == 1: + points_array = points_array.reshape(-1, 3) + + # Create connectivity if not provided (sequential connections) + if connections is None: + n_points = len(points_array) + if n_points < 2: + raise ValueError("At least 2 points are required to create lines") + # Create sequential line segments + connections_array = np.array([[i, i + 1] for i in range(n_points - 1)]) + else: + connections_array = np.asarray(connections) + + # Ensure connections are 2D + if connections_array.ndim == 1: + connections_array = connections_array.reshape(-1, 2) + + # Create PyVista PolyData with lines + lines = pv.PolyData() + lines.points = points_array + + # Build the lines array for PyVista + # Format: [n_points_in_line, point_idx1, point_idx2, ...] + lines_array = [] + for conn in connections_array: + lines_array.extend([2, conn[0], conn[1]]) + + lines.lines = np.array(lines_array, dtype=np.int64) + + # Add lines to the scene + actor = self._pl.scene.add_mesh( + lines, + color=color, + line_width=width, + **kwargs + ) + + return actor + + def add_planes( + self, + center: tuple[float, float, float] = (0.0, 0.0, 0.0), + normal: tuple[float, float, float] = (0.0, 0.0, 1.0), + i_size: float = 1.0, + j_size: float = 1.0, + **kwargs + ) -> "pv.Actor": + """Add a plane to the scene. + + Parameters + ---------- + center : Tuple[float, float, float], default: (0.0, 0.0, 0.0) + Center point of the plane (x, y, z). + normal : Tuple[float, float, float], default: (0.0, 0.0, 1.0) + Normal vector of the plane (x, y, z). + i_size : float, default: 1.0 + Size of the plane in the i direction. + j_size : float, default: 1.0 + Size of the plane in the j direction. + **kwargs : dict + Additional keyword arguments passed to PyVista's add_mesh method + (e.g., color, opacity). + + Returns + ------- + pv.Actor + PyVista actor representing the added plane. + """ + # Create a PyVista plane + plane = pv.Plane( + center=center, + direction=normal, + i_size=i_size, + j_size=j_size, + ) + + # Add plane to the scene + actor = self._pl.scene.add_mesh(plane, **kwargs) + + return actor + + def add_text( + self, + text: str, + position: Union[tuple[float, float], tuple[float, float, float]], + font_size: int = 12, + color: str = "white", + **kwargs + ) -> "pv.Actor": + """Add text to the scene. + + Parameters + ---------- + text : str + Text string to display. + position : Union[Tuple[float, float], Tuple[float, float, float]] + Position for the text. Can be 2D (x, y) for screen coordinates + or 3D (x, y, z) for world coordinates. + font_size : int, default: 12 + Font size for the text. + color : str, default: "white" + Color of the text. + **kwargs : dict + Additional keyword arguments passed to PyVista's add_text or + add_point_labels method. + + Returns + ------- + pv.Actor + PyVista actor representing the added text. + """ + # Determine if position is 2D or 3D + if len(position) == 2: + # 2D screen coordinates - use add_text + actor = self._pl.scene.add_text( + text, + position=position, + font_size=font_size, + color=color, + **kwargs + ) + elif len(position) == 3: + # 3D world coordinates - use add_point_labels + import numpy as np + points = np.array([position]) + actor = self._pl.scene.add_point_labels( + points, + [text], + font_size=font_size, + text_color=color, + **kwargs + ) + else: + raise ValueError( + f"Position must be 2D (x, y) or 3D (x, y, z), got {len(position)} dimensions" + ) + + return actor diff --git a/src/ansys/tools/visualization_interface/plotter.py b/src/ansys/tools/visualization_interface/plotter.py index f036826a6..6b9418aca 100644 --- a/src/ansys/tools/visualization_interface/plotter.py +++ b/src/ansys/tools/visualization_interface/plotter.py @@ -21,7 +21,7 @@ # SOFTWARE. """Module for the Plotter class.""" -from typing import Any, List, Optional +from typing import Any, List, Optional, Tuple, Union from ansys.tools.visualization_interface.backends._base import BaseBackend from ansys.tools.visualization_interface.backends.pyvista.pyvista import PyVistaBackend @@ -227,3 +227,232 @@ def animate( scalar_bar_args=scalar_bar_args, **plot_kwargs, ) + + def add_points( + self, + points: Union[List, Any], + color: str = "red", + size: float = 10.0, + **kwargs + ) -> Any: + """Add point markers to the scene. + + This method provides a backend-agnostic way to add point markers to the + visualization scene. The points will be rendered using the active backend's + native point rendering capabilities. + + Parameters + ---------- + points : Union[List, Any] + Points to add. Can be a list of coordinates or array-like object. + Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + color : str, default: "red" + Color of the points. Can be a color name (e.g., 'red', 'blue') + or hex color code (e.g., '#FF0000'). + size : float, default: 10.0 + Size of the point markers in pixels or display units + (interpretation depends on backend). + **kwargs : dict + Additional backend-specific keyword arguments for advanced customization. + + Returns + ------- + Any + Backend-specific actor or object representing the added points. + Can be used for further manipulation or removal. + + Examples + -------- + Add simple point markers at three locations: + + >>> from ansys.tools.visualization_interface import Plotter + >>> plotter = Plotter() + >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0]] + >>> plotter.add_points(points, color='blue', size=15) + >>> plotter.show() + + Add points with custom styling: + + >>> import numpy as np + >>> points = np.random.rand(100, 3) + >>> plotter.add_points(points, color='yellow', size=8) + >>> plotter.show() + """ + return self._backend.add_points(points=points, color=color, size=size, **kwargs) + + def add_lines( + self, + points: Union[List, Any], + connections: Optional[Union[List, Any]] = None, + color: str = "white", + width: float = 1.0, + **kwargs + ) -> Any: + """Add line segments to the scene. + + This method provides a backend-agnostic way to add lines to the + visualization scene. Lines can connect points sequentially or based + on explicit connectivity information. + + Parameters + ---------- + points : Union[List, Any] + Points defining the lines. Can be a list of coordinates or array-like object. + Expected format: [[x1, y1, z1], [x2, y2, z2], ...] or Nx3 array. + connections : Optional[Union[List, Any]], default: None + Line connectivity. If None, connects points sequentially (0->1, 1->2, 2->3, ...). + If provided, should define line segments as pairs of point indices: + [[start_idx1, end_idx1], [start_idx2, end_idx2], ...] or Mx2 array + where M is the number of line segments. + color : str, default: "white" + Color of the lines. Can be a color name or hex color code. + width : float, default: 1.0 + Width of the lines in pixels or display units (interpretation depends on backend). + **kwargs : dict + Additional backend-specific keyword arguments for advanced customization. + + Returns + ------- + Any + Backend-specific actor or object representing the added lines. + Can be used for further manipulation or removal. + + Examples + -------- + Add a line connecting points sequentially: + + >>> from ansys.tools.visualization_interface import Plotter + >>> plotter = Plotter() + >>> points = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]] + >>> plotter.add_lines(points, color='green', width=2.0) + >>> plotter.show() + + Add specific line segments with explicit connectivity: + + >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]] + >>> connections = [[0, 1], [2, 3], [0, 2]] # Connect specific pairs + >>> plotter.add_lines(points, connections=connections, color='red', width=3.0) + >>> plotter.show() + """ + return self._backend.add_lines( + points=points, connections=connections, color=color, width=width, **kwargs + ) + + def add_planes( + self, + center: Tuple[float, float, float] = (0.0, 0.0, 0.0), + normal: Tuple[float, float, float] = (0.0, 0.0, 1.0), + i_size: float = 1.0, + j_size: float = 1.0, + **kwargs + ) -> Any: + """Add a plane to the scene. + + This method provides a backend-agnostic way to add plane objects to the + visualization scene. Planes are useful for showing reference planes, + symmetry planes, or cutting planes. + + Parameters + ---------- + center : Tuple[float, float, float], default: (0.0, 0.0, 0.0) + Center point of the plane in 3D space (x, y, z). + normal : Tuple[float, float, float], default: (0.0, 0.0, 1.0) + Normal vector of the plane (x, y, z). The vector will be normalized + by the backend if needed. + i_size : float, default: 1.0 + Size of the plane in the i direction (local coordinate system). + j_size : float, default: 1.0 + Size of the plane in the j direction (local coordinate system). + **kwargs : dict + Additional backend-specific keyword arguments for advanced customization + (e.g., color, opacity, resolution). + + Returns + ------- + Any + Backend-specific actor or object representing the added plane. + Can be used for further manipulation or removal. + + Examples + -------- + Add a horizontal plane at z=0: + + >>> from ansys.tools.visualization_interface import Plotter + >>> plotter = Plotter() + >>> plotter.add_planes(center=(0, 0, 0), normal=(0, 0, 1), i_size=2.0, j_size=2.0) + >>> plotter.show() + + Add a vertical plane with custom styling: + + >>> plotter.add_planes( + ... center=(1, 0, 0), + ... normal=(1, 0, 0), + ... i_size=3.0, + ... j_size=3.0, + ... color='lightblue', + ... opacity=0.5 + ... ) + >>> plotter.show() + """ + return self._backend.add_planes( + center=center, normal=normal, i_size=i_size, j_size=j_size, **kwargs + ) + + def add_text( + self, + text: str, + position: Union[Tuple[float, float], Tuple[float, float, float]], + font_size: int = 12, + color: str = "white", + **kwargs + ) -> Any: + """Add text to the scene. + + This method provides a backend-agnostic way to add text labels to the + visualization scene. Text can be positioned in 2D screen coordinates or + 3D world coordinates depending on the backend capabilities. + + Parameters + ---------- + text : str + Text string to display. + position : Union[Tuple[float, float], Tuple[float, float, float]] + Position for the text. Can be: + - 2D tuple (x, y) for screen/viewport coordinates (pixels from bottom-left) + - 3D tuple (x, y, z) for world coordinates (backend-dependent support) + font_size : int, default: 12 + Font size for the text in points. + color : str, default: "white" + Color of the text. Can be a color name or hex color code. + **kwargs : dict + Additional backend-specific keyword arguments for advanced customization + (e.g., font_family, bold, italic, shadow). + + Returns + ------- + Any + Backend-specific actor or object representing the added text. + Can be used for further manipulation or removal. + + Examples + -------- + Add text at a screen position: + + >>> from ansys.tools.visualization_interface import Plotter + >>> plotter = Plotter() + >>> plotter.add_text("Title", position=(10, 10), font_size=18, color='yellow') + >>> plotter.show() + + Add text at a 3D world coordinate: + + >>> plotter.add_text( + ... "Point A", + ... position=(1.0, 2.0, 3.0), + ... font_size=14, + ... color='red' + ... ) + >>> plotter.show() + """ + return self._backend.add_text( + text=text, position=position, font_size=font_size, color=color, **kwargs + ) From d85217f9b1d87ad5a484a63543a6cb440426df93 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 6 Feb 2026 13:27:06 +0100 Subject: [PATCH 02/31] fix: unneeded docs --- examples/00-basic-pyvista-examples/README.txt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/examples/00-basic-pyvista-examples/README.txt b/examples/00-basic-pyvista-examples/README.txt index f89d7b0bc..54cac51e4 100644 --- a/examples/00-basic-pyvista-examples/README.txt +++ b/examples/00-basic-pyvista-examples/README.txt @@ -2,15 +2,3 @@ Basic usage examples ==================== These examples show how to use the general plotter included in the Visualization Interface Tool. - -New: Customization APIs ------------------------- - -See ``customization_api.py`` for examples of the new backend-agnostic customization APIs: - -- ``add_points()`` - Add point markers -- ``add_lines()`` - Add line segments -- ``add_planes()`` - Add reference planes -- ``add_text()`` - Add text labels - -These APIs work consistently across different backends. \ No newline at end of file From 082546ef36849e23471a21785173b93518d1f6d1 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:28:03 +0000 Subject: [PATCH 03/31] chore: adding changelog file 465.miscellaneous.md [dependabot-skip] --- doc/changelog.d/465.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/465.miscellaneous.md diff --git a/doc/changelog.d/465.miscellaneous.md b/doc/changelog.d/465.miscellaneous.md new file mode 100644 index 000000000..bb700c6e8 --- /dev/null +++ b/doc/changelog.d/465.miscellaneous.md @@ -0,0 +1 @@ +Feat: Add customization APIs From 43ab20612adc902bbe545c060b3afa0089dffe88 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 6 Feb 2026 13:46:58 +0100 Subject: [PATCH 04/31] fix: bugs and docs --- .../backends/pyvista/pyvista.py | 26 +++++++++++++------ .../backends/pyvista/pyvista_interface.py | 10 ++++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py index 8459290f4..d4b897b64 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py @@ -946,14 +946,24 @@ def add_text( **kwargs ) elif len(position) == 3: - # 3D world coordinates - use add_point_labels - import numpy as np - points = np.array([position]) - actor = self._pl.scene.add_point_labels( - points, - [text], - font_size=font_size, - text_color=color, + # 3D world coordinates - create 3D text mesh + # We use Text3D instead of add_point_labels to avoid a bug in PyVista + # where it tries to access a non-existent _actors attribute + + # Create 3D text object + text_mesh = pv.Text3D(text, depth=0.0) + + # Scale text based on font_size (approximate scaling factor) + scale_factor = font_size / 100.0 + text_mesh.points *= scale_factor + + # Translate text to desired position + text_mesh.translate(position, inplace=True) + + # Add text mesh to scene with specified color + actor = self._pl.scene.add_mesh( + text_mesh, + color=color, **kwargs ) else: diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py index 64baab1ba..d2b510a84 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py @@ -443,11 +443,19 @@ def show( jupyter_backend = "html" # Enabling anti-aliasing by default on scene - self.scene.enable_anti_aliasing("ssaa") + # Only enable if render_window is available (not None) + if hasattr(self.scene, 'render_window') and self.scene.render_window is not None: + self.scene.enable_anti_aliasing("ssaa") # If screenshot is requested, set off_screen to True for the plotter if kwargs.get("screenshot") is not None: self.scene.off_screen = True + + # In off-screen mode (e.g., sphinx-gallery), prevent auto-close + # so the plotter can be reused + if self.scene.off_screen and 'auto_close' not in kwargs: + kwargs['auto_close'] = False + if jupyter_backend: # Remove jupyter_backend from show options since we pass it manually kwargs.pop("jupyter_backend", None) From 9626ba97f0f0e016c554fa1de85e825d79ba6280 Mon Sep 17 00:00:00 2001 From: moe-ad Date: Sun, 8 Feb 2026 18:42:20 +0100 Subject: [PATCH 05/31] feat: add more customization apis --- .../visualization_interface/backends/_base.py | 89 +++++++++- .../backends/plotly/plotly_interface.py | 100 ++++++++++- .../backends/pyvista/pyvista.py | 139 ++++++++++++++- .../tools/visualization_interface/plotter.py | 160 +++++++++++++++++- 4 files changed, 477 insertions(+), 11 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/_base.py b/src/ansys/tools/visualization_interface/backends/_base.py index 017e09e75..2cedd46fb 100644 --- a/src/ansys/tools/visualization_interface/backends/_base.py +++ b/src/ansys/tools/visualization_interface/backends/_base.py @@ -141,7 +141,7 @@ def add_planes( def add_text( self, text: str, - position: Union[Tuple[float, float], Tuple[float, float, float]], + position: Union[Tuple[float, float], Tuple[float, float, float], str], font_size: int = 12, color: str = "white", **kwargs @@ -152,9 +152,11 @@ def add_text( ---------- text : str Text string to display. - position : Union[Tuple[float, float], Tuple[float, float, float]] - Position for the text. Can be 2D (x, y) for screen coordinates - or 3D (x, y, z) for world coordinates. + position : Union[Tuple[float, float], Tuple[float, float, float], str] + Position for the text. Can be 2D (x, y) for screen coordinates, + 3D (x, y, z) for world coordinates, or a string position like + 'upper_left', 'upper_right', 'lower_left', 'lower_right', + 'upper_edge', 'lower_edge' (backend-dependent support). font_size : int, default: 12 Font size for the text. color : str, default: "white" @@ -168,3 +170,82 @@ def add_text( Backend-specific actor or object representing the added text. """ raise NotImplementedError("add_text method must be implemented") + + @abstractmethod + def add_mesh( + self, + mesh: Any, + scalars: Optional[Union[str, Any]] = None, + scalar_bar_args: Optional[dict] = None, + show_edges: bool = False, + nan_color: str = "grey", + **kwargs + ) -> Any: + """Add a mesh to the scene. + + Parameters + ---------- + mesh : Any + Mesh object to add. Can be a PyVista mesh (UnstructuredGrid, PolyData, + MultiBlock) or other backend-specific mesh type. + scalars : Optional[Union[str, Any]], default: None + Scalars to use for coloring. Can be a string name of an array in + the mesh, or an array-like object with scalar values. + scalar_bar_args : Optional[dict], default: None + Arguments for the scalar bar (colorbar). Common keys include: + - 'title': Title for the scalar bar + - 'vertical': Whether to orient vertically (default False) + show_edges : bool, default: False + Whether to show mesh edges. + nan_color : str, default: "grey" + Color to use for NaN values in scalars. + **kwargs : dict + Additional backend-specific keyword arguments. + + Returns + ------- + Any + Backend-specific actor or object representing the added mesh. + """ + raise NotImplementedError("add_mesh method must be implemented") + + @abstractmethod + def add_point_labels( + self, + points: Union[List, Any], + labels: List[str], + font_size: int = 12, + point_size: float = 5.0, + **kwargs + ) -> Any: + """Add labels at 3D point locations. + + Parameters + ---------- + points : Union[List, Any] + Points where labels should be placed. Can be a list of coordinates + or array-like object. Expected format: [[x1, y1, z1], ...] or Nx3 array. + labels : List[str] + List of label strings to display at each point. + font_size : int, default: 12 + Font size for the labels. + point_size : float, default: 5.0 + Size of the point markers shown with labels. + **kwargs : dict + Additional backend-specific keyword arguments. + + Returns + ------- + Any + Backend-specific actor or object representing the added labels. + """ + raise NotImplementedError("add_point_labels method must be implemented") + + @abstractmethod + def clear(self) -> None: + """Clear all actors from the scene. + + This method removes all previously added objects (meshes, points, lines, + text, etc.) from the visualization scene. + """ + raise NotImplementedError("clear method must be implemented") diff --git a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py index aed20256a..e55ab4aca 100644 --- a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py +++ b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py @@ -21,7 +21,7 @@ # SOFTWARE. """Plotly backend interface for visualization.""" -from typing import Any, Iterable, Union +from typing import Any, Iterable, List, Optional, Tuple, Union import plotly.graph_objects as go import pyvista as pv @@ -484,3 +484,101 @@ def add_text(self, text, position, font_size=12, color="white", **kwargs): ) self._fig.add_annotation(annotation) return annotation + + def add_mesh( + self, + mesh: Any, + scalars: Optional[Union[str, Any]] = None, + scalar_bar_args: Optional[dict] = None, + show_edges: bool = False, + nan_color: str = "grey", + **kwargs + ) -> Any: + """Add a mesh to the scene. + + Note: This is a simplified implementation. For full mesh rendering + in Plotly, see the plot() method which converts meshes to Mesh3d. + + Parameters + ---------- + mesh : Any + Mesh object to add. + scalars : Optional[Union[str, Any]], default: None + Scalars to use for coloring. + scalar_bar_args : Optional[dict], default: None + Arguments for the scalar bar. + show_edges : bool, default: False + Whether to show mesh edges. + nan_color : str, default: "grey" + Color to use for NaN values. + **kwargs : dict + Additional keyword arguments. + + Returns + ------- + Any + Plotly trace representing the mesh. + """ + # Use the existing conversion method to convert the mesh + mesh3d = self._pv_to_mesh3d(mesh) + + if isinstance(mesh3d, list): + # MultiBlock case - add all traces + for trace in mesh3d: + self._fig.add_trace(trace) + return mesh3d + else: + self._fig.add_trace(mesh3d) + return mesh3d + + def add_point_labels( + self, + points: Union[List, Any], + labels: List[str], + font_size: int = 12, + point_size: float = 5.0, + **kwargs + ) -> Any: + """Add labels at 3D point locations. + + Parameters + ---------- + points : Union[List, Any] + Points where labels should be placed. + labels : List[str] + List of label strings to display at each point. + font_size : int, default: 12 + Font size for the labels. + point_size : float, default: 5.0 + Size of the point markers shown with labels. + **kwargs : dict + Additional keyword arguments. + + Returns + ------- + Any + Plotly trace representing the labels. + """ + import numpy as np + + points_array = np.asarray(points) + if points_array.ndim == 1: + points_array = points_array.reshape(-1, 3) + + # Create a scatter trace with both markers and text + trace = go.Scatter3d( + x=points_array[:, 0], + y=points_array[:, 1], + z=points_array[:, 2], + mode='markers+text', + text=labels, + textfont=dict(size=font_size), + marker=dict(size=point_size), + **kwargs + ) + self._fig.add_trace(trace) + return trace + + def clear(self) -> None: + """Clear all traces from the figure.""" + self._fig.data = [] diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py index d4b897b64..a297605f9 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py @@ -583,6 +583,7 @@ def __init__( use_qt: Optional[bool] = False, show_qt: Optional[bool] = False, custom_picker: AbstractPicker = None, + **plotter_kwargs, ) -> None: """Initialize the generic plotter.""" super().__init__( @@ -592,7 +593,8 @@ def __init__( plot_picked_names, use_qt=use_qt, show_qt=show_qt, - custom_picker=custom_picker + custom_picker=custom_picker, + **plotter_kwargs, ) @property @@ -919,9 +921,12 @@ def add_text( ---------- text : str Text string to display. - position : Union[Tuple[float, float], Tuple[float, float, float]] - Position for the text. Can be 2D (x, y) for screen coordinates - or 3D (x, y, z) for world coordinates. + position : Union[Tuple[float, float], Tuple[float, float, float], str] + Position for the text. Can be: + - 2D tuple (x, y) for screen coordinates + - 3D tuple (x, y, z) for world coordinates + - String position like 'upper_left', 'upper_right', 'lower_left', + 'lower_right', 'upper_edge', 'lower_edge' (PyVista-specific) font_size : int, default: 12 Font size for the text. color : str, default: "white" @@ -935,6 +940,17 @@ def add_text( pv.Actor PyVista actor representing the added text. """ + # Handle string positions (PyVista-specific) + if isinstance(position, str): + actor = self._pl.scene.add_text( + text, + position=position, + font_size=font_size, + color=color, + **kwargs + ) + return actor + # Determine if position is 2D or 3D if len(position) == 2: # 2D screen coordinates - use add_text @@ -972,3 +988,118 @@ def add_text( ) return actor + + def add_mesh( + self, + mesh: Any, + scalars: Optional[Union[str, Any]] = None, + scalar_bar_args: Optional[dict] = None, + show_edges: bool = False, + nan_color: str = "grey", + **kwargs + ) -> "pv.Actor": + """Add a mesh to the scene. + + Parameters + ---------- + mesh : Any + Mesh object to add. Accepts PyVista mesh types including + UnstructuredGrid, PolyData, and MultiBlock. + scalars : Optional[Union[str, Any]], default: None + Scalars to use for coloring. Can be a string name of an array in + the mesh, or an array-like object with scalar values. + scalar_bar_args : Optional[dict], default: None + Arguments for the scalar bar (colorbar). Common keys include: + - 'title': Title for the scalar bar + - 'vertical': Whether to orient vertically + show_edges : bool, default: False + Whether to show mesh edges. + nan_color : str, default: "grey" + Color to use for NaN values in scalars. + **kwargs : dict + Additional keyword arguments passed to PyVista's add_mesh method. + + Returns + ------- + pv.Actor + PyVista actor representing the added mesh. + """ + # Build kwargs for PyVista add_mesh + add_mesh_kwargs = { + "show_edges": show_edges, + "nan_color": nan_color, + **kwargs + } + + # Add scalars if provided + if scalars is not None: + add_mesh_kwargs["scalars"] = scalars + + # Add scalar bar args if provided + if scalar_bar_args is not None: + add_mesh_kwargs["scalar_bar_args"] = scalar_bar_args + + # Add mesh to the scene + actor = self._pl.scene.add_mesh(mesh, **add_mesh_kwargs) + + return actor + + def add_point_labels( + self, + points: Union[List, Any], + labels: List[str], + font_size: int = 12, + point_size: float = 5.0, + **kwargs + ) -> "pv.Actor": + """Add labels at 3D point locations. + + Parameters + ---------- + points : Union[List, Any] + Points where labels should be placed. Can be a list of coordinates + or array-like object. Expected format: [[x1, y1, z1], ...] or Nx3 array. + labels : List[str] + List of label strings to display at each point. + font_size : int, default: 12 + Font size for the labels. + point_size : float, default: 5.0 + Size of the point markers shown with labels. + **kwargs : dict + Additional keyword arguments passed to PyVista's add_point_labels method. + + Returns + ------- + pv.Actor + PyVista actor representing the added labels. + """ + import numpy as np + + # Convert points to numpy array if needed + points_array = np.asarray(points) + + # Ensure points are 2D with shape (N, 3) + if points_array.ndim == 1: + points_array = points_array.reshape(-1, 3) + + # Create PyVista PolyData from points + point_cloud = pv.PolyData(points_array) + + # Add point labels to the scene + actor = self._pl.scene.add_point_labels( + point_cloud, + labels, + font_size=font_size, + point_size=point_size, + **kwargs + ) + + return actor + + def clear(self) -> None: + """Clear all actors from the scene. + + This method removes all previously added objects (meshes, points, lines, + text, etc.) from the visualization scene. + """ + self._pl.scene.clear() diff --git a/src/ansys/tools/visualization_interface/plotter.py b/src/ansys/tools/visualization_interface/plotter.py index 6b9418aca..9af13d637 100644 --- a/src/ansys/tools/visualization_interface/plotter.py +++ b/src/ansys/tools/visualization_interface/plotter.py @@ -401,7 +401,7 @@ def add_planes( def add_text( self, text: str, - position: Union[Tuple[float, float], Tuple[float, float, float]], + position: Union[Tuple[float, float], Tuple[float, float, float], str], font_size: int = 12, color: str = "white", **kwargs @@ -416,10 +416,12 @@ def add_text( ---------- text : str Text string to display. - position : Union[Tuple[float, float], Tuple[float, float, float]] + position : Union[Tuple[float, float], Tuple[float, float, float], str] Position for the text. Can be: - 2D tuple (x, y) for screen/viewport coordinates (pixels from bottom-left) - 3D tuple (x, y, z) for world coordinates (backend-dependent support) + - String position like 'upper_left', 'upper_right', 'lower_left', + 'lower_right', 'upper_edge', 'lower_edge' (backend-dependent support) font_size : int, default: 12 Font size for the text in points. color : str, default: "white" @@ -456,3 +458,157 @@ def add_text( return self._backend.add_text( text=text, position=position, font_size=font_size, color=color, **kwargs ) + + def add_mesh( + self, + mesh: Any, + scalars: Optional[Union[str, Any]] = None, + scalar_bar_args: Optional[dict] = None, + show_edges: bool = False, + nan_color: str = "grey", + **kwargs + ) -> Any: + """Add a mesh to the scene. + + This method provides a backend-agnostic way to add mesh objects to the + visualization scene. Meshes can be colored by scalar values with + customizable color bars. + + Parameters + ---------- + mesh : Any + Mesh object to add. Can be a PyVista mesh (UnstructuredGrid, PolyData, + MultiBlock) or other backend-specific mesh type. + scalars : Optional[Union[str, Any]], default: None + Scalars to use for coloring. Can be a string name of an array in + the mesh, or an array-like object with scalar values. + scalar_bar_args : Optional[dict], default: None + Arguments for the scalar bar (colorbar). Common keys include: + - 'title': Title for the scalar bar + - 'vertical': Whether to orient vertically (default False) + show_edges : bool, default: False + Whether to show mesh edges. + nan_color : str, default: "grey" + Color to use for NaN values in scalars. + **kwargs : dict + Additional backend-specific keyword arguments for advanced customization + (e.g., cmap, opacity, lighting). + + Returns + ------- + Any + Backend-specific actor or object representing the added mesh. + Can be used for further manipulation or removal. + + Examples + -------- + Add a simple mesh: + + >>> from ansys.tools.visualization_interface import Plotter + >>> import pyvista as pv + >>> plotter = Plotter() + >>> mesh = pv.Sphere() + >>> plotter.add_mesh(mesh, show_edges=True) + >>> plotter.show() + + Add a mesh with scalar coloring: + + >>> mesh = pv.Sphere() + >>> mesh['elevation'] = mesh.points[:, 2] + >>> plotter.add_mesh( + ... mesh, + ... scalars='elevation', + ... scalar_bar_args={'title': 'Elevation (m)'}, + ... cmap='viridis' + ... ) + >>> plotter.show() + """ + return self._backend.add_mesh( + mesh=mesh, + scalars=scalars, + scalar_bar_args=scalar_bar_args, + show_edges=show_edges, + nan_color=nan_color, + **kwargs + ) + + def add_point_labels( + self, + points: Union[List, Any], + labels: List[str], + font_size: int = 12, + point_size: float = 5.0, + **kwargs + ) -> Any: + """Add labels at 3D point locations. + + This method provides a backend-agnostic way to add text labels at + specific 3D coordinates in the visualization scene. Labels are + displayed next to marker points. + + Parameters + ---------- + points : Union[List, Any] + Points where labels should be placed. Can be a list of coordinates + or array-like object. Expected format: [[x1, y1, z1], ...] or Nx3 array. + labels : List[str] + List of label strings to display at each point. Must have the same + length as points. + font_size : int, default: 12 + Font size for the labels. + point_size : float, default: 5.0 + Size of the point markers shown with labels. + **kwargs : dict + Additional backend-specific keyword arguments for advanced customization + (e.g., text_color, shape, fill_shape). + + Returns + ------- + Any + Backend-specific actor or object representing the added labels. + Can be used for further manipulation or removal. + + Examples + -------- + Add labels at specific locations: + + >>> from ansys.tools.visualization_interface import Plotter + >>> plotter = Plotter() + >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0]] + >>> labels = ['Origin', 'X-axis', 'Y-axis'] + >>> plotter.add_point_labels(points, labels, font_size=14) + >>> plotter.show() + + Add labels with custom styling: + + >>> plotter.add_point_labels( + ... points, + ... labels, + ... font_size=16, + ... point_size=10, + ... text_color='yellow', + ... shape='rounded_rect' + ... ) + >>> plotter.show() + """ + return self._backend.add_point_labels( + points=points, labels=labels, font_size=font_size, point_size=point_size, **kwargs + ) + + def clear(self) -> None: + """Clear all actors from the scene. + + This method removes all previously added objects (meshes, points, lines, + text, etc.) from the visualization scene. + + Examples + -------- + >>> from ansys.tools.visualization_interface import Plotter + >>> import pyvista as pv + >>> plotter = Plotter() + >>> plotter.add_mesh(pv.Sphere()) + >>> plotter.clear() # Remove all objects + >>> plotter.add_mesh(pv.Cube()) # Add different object + >>> plotter.show() + """ + self._backend.clear() From 420558431e495a7d881f9b5e698e21e369386d5d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:42:59 +0000 Subject: [PATCH 06/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../visualization_interface/backends/plotly/plotly_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py index e55ab4aca..1bd203c37 100644 --- a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py +++ b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py @@ -21,7 +21,7 @@ # SOFTWARE. """Plotly backend interface for visualization.""" -from typing import Any, Iterable, List, Optional, Tuple, Union +from typing import Any, Iterable, List, Optional, Union import plotly.graph_objects as go import pyvista as pv From 44a33a826f8e91c8d3fc3ab0a50eea2ad8ecdbfc Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Tue, 10 Feb 2026 11:59:02 +0100 Subject: [PATCH 07/31] fix: Revert API changes --- .../visualization_interface/backends/_base.py | 79 --------- .../backends/plotly/plotly_interface.py | 100 +----------- .../backends/pyvista/pyvista.py | 115 ------------- .../tools/visualization_interface/plotter.py | 154 ------------------ 4 files changed, 1 insertion(+), 447 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/_base.py b/src/ansys/tools/visualization_interface/backends/_base.py index 2cedd46fb..8698877e0 100644 --- a/src/ansys/tools/visualization_interface/backends/_base.py +++ b/src/ansys/tools/visualization_interface/backends/_base.py @@ -170,82 +170,3 @@ def add_text( Backend-specific actor or object representing the added text. """ raise NotImplementedError("add_text method must be implemented") - - @abstractmethod - def add_mesh( - self, - mesh: Any, - scalars: Optional[Union[str, Any]] = None, - scalar_bar_args: Optional[dict] = None, - show_edges: bool = False, - nan_color: str = "grey", - **kwargs - ) -> Any: - """Add a mesh to the scene. - - Parameters - ---------- - mesh : Any - Mesh object to add. Can be a PyVista mesh (UnstructuredGrid, PolyData, - MultiBlock) or other backend-specific mesh type. - scalars : Optional[Union[str, Any]], default: None - Scalars to use for coloring. Can be a string name of an array in - the mesh, or an array-like object with scalar values. - scalar_bar_args : Optional[dict], default: None - Arguments for the scalar bar (colorbar). Common keys include: - - 'title': Title for the scalar bar - - 'vertical': Whether to orient vertically (default False) - show_edges : bool, default: False - Whether to show mesh edges. - nan_color : str, default: "grey" - Color to use for NaN values in scalars. - **kwargs : dict - Additional backend-specific keyword arguments. - - Returns - ------- - Any - Backend-specific actor or object representing the added mesh. - """ - raise NotImplementedError("add_mesh method must be implemented") - - @abstractmethod - def add_point_labels( - self, - points: Union[List, Any], - labels: List[str], - font_size: int = 12, - point_size: float = 5.0, - **kwargs - ) -> Any: - """Add labels at 3D point locations. - - Parameters - ---------- - points : Union[List, Any] - Points where labels should be placed. Can be a list of coordinates - or array-like object. Expected format: [[x1, y1, z1], ...] or Nx3 array. - labels : List[str] - List of label strings to display at each point. - font_size : int, default: 12 - Font size for the labels. - point_size : float, default: 5.0 - Size of the point markers shown with labels. - **kwargs : dict - Additional backend-specific keyword arguments. - - Returns - ------- - Any - Backend-specific actor or object representing the added labels. - """ - raise NotImplementedError("add_point_labels method must be implemented") - - @abstractmethod - def clear(self) -> None: - """Clear all actors from the scene. - - This method removes all previously added objects (meshes, points, lines, - text, etc.) from the visualization scene. - """ - raise NotImplementedError("clear method must be implemented") diff --git a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py index 1bd203c37..aed20256a 100644 --- a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py +++ b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py @@ -21,7 +21,7 @@ # SOFTWARE. """Plotly backend interface for visualization.""" -from typing import Any, Iterable, List, Optional, Union +from typing import Any, Iterable, Union import plotly.graph_objects as go import pyvista as pv @@ -484,101 +484,3 @@ def add_text(self, text, position, font_size=12, color="white", **kwargs): ) self._fig.add_annotation(annotation) return annotation - - def add_mesh( - self, - mesh: Any, - scalars: Optional[Union[str, Any]] = None, - scalar_bar_args: Optional[dict] = None, - show_edges: bool = False, - nan_color: str = "grey", - **kwargs - ) -> Any: - """Add a mesh to the scene. - - Note: This is a simplified implementation. For full mesh rendering - in Plotly, see the plot() method which converts meshes to Mesh3d. - - Parameters - ---------- - mesh : Any - Mesh object to add. - scalars : Optional[Union[str, Any]], default: None - Scalars to use for coloring. - scalar_bar_args : Optional[dict], default: None - Arguments for the scalar bar. - show_edges : bool, default: False - Whether to show mesh edges. - nan_color : str, default: "grey" - Color to use for NaN values. - **kwargs : dict - Additional keyword arguments. - - Returns - ------- - Any - Plotly trace representing the mesh. - """ - # Use the existing conversion method to convert the mesh - mesh3d = self._pv_to_mesh3d(mesh) - - if isinstance(mesh3d, list): - # MultiBlock case - add all traces - for trace in mesh3d: - self._fig.add_trace(trace) - return mesh3d - else: - self._fig.add_trace(mesh3d) - return mesh3d - - def add_point_labels( - self, - points: Union[List, Any], - labels: List[str], - font_size: int = 12, - point_size: float = 5.0, - **kwargs - ) -> Any: - """Add labels at 3D point locations. - - Parameters - ---------- - points : Union[List, Any] - Points where labels should be placed. - labels : List[str] - List of label strings to display at each point. - font_size : int, default: 12 - Font size for the labels. - point_size : float, default: 5.0 - Size of the point markers shown with labels. - **kwargs : dict - Additional keyword arguments. - - Returns - ------- - Any - Plotly trace representing the labels. - """ - import numpy as np - - points_array = np.asarray(points) - if points_array.ndim == 1: - points_array = points_array.reshape(-1, 3) - - # Create a scatter trace with both markers and text - trace = go.Scatter3d( - x=points_array[:, 0], - y=points_array[:, 1], - z=points_array[:, 2], - mode='markers+text', - text=labels, - textfont=dict(size=font_size), - marker=dict(size=point_size), - **kwargs - ) - self._fig.add_trace(trace) - return trace - - def clear(self) -> None: - """Clear all traces from the figure.""" - self._fig.data = [] diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py index 8f8d83591..5a810180f 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py @@ -990,118 +990,3 @@ def add_text( ) return actor - - def add_mesh( - self, - mesh: Any, - scalars: Optional[Union[str, Any]] = None, - scalar_bar_args: Optional[dict] = None, - show_edges: bool = False, - nan_color: str = "grey", - **kwargs - ) -> "pv.Actor": - """Add a mesh to the scene. - - Parameters - ---------- - mesh : Any - Mesh object to add. Accepts PyVista mesh types including - UnstructuredGrid, PolyData, and MultiBlock. - scalars : Optional[Union[str, Any]], default: None - Scalars to use for coloring. Can be a string name of an array in - the mesh, or an array-like object with scalar values. - scalar_bar_args : Optional[dict], default: None - Arguments for the scalar bar (colorbar). Common keys include: - - 'title': Title for the scalar bar - - 'vertical': Whether to orient vertically - show_edges : bool, default: False - Whether to show mesh edges. - nan_color : str, default: "grey" - Color to use for NaN values in scalars. - **kwargs : dict - Additional keyword arguments passed to PyVista's add_mesh method. - - Returns - ------- - pv.Actor - PyVista actor representing the added mesh. - """ - # Build kwargs for PyVista add_mesh - add_mesh_kwargs = { - "show_edges": show_edges, - "nan_color": nan_color, - **kwargs - } - - # Add scalars if provided - if scalars is not None: - add_mesh_kwargs["scalars"] = scalars - - # Add scalar bar args if provided - if scalar_bar_args is not None: - add_mesh_kwargs["scalar_bar_args"] = scalar_bar_args - - # Add mesh to the scene - actor = self._pl.scene.add_mesh(mesh, **add_mesh_kwargs) - - return actor - - def add_point_labels( - self, - points: Union[List, Any], - labels: List[str], - font_size: int = 12, - point_size: float = 5.0, - **kwargs - ) -> "pv.Actor": - """Add labels at 3D point locations. - - Parameters - ---------- - points : Union[List, Any] - Points where labels should be placed. Can be a list of coordinates - or array-like object. Expected format: [[x1, y1, z1], ...] or Nx3 array. - labels : List[str] - List of label strings to display at each point. - font_size : int, default: 12 - Font size for the labels. - point_size : float, default: 5.0 - Size of the point markers shown with labels. - **kwargs : dict - Additional keyword arguments passed to PyVista's add_point_labels method. - - Returns - ------- - pv.Actor - PyVista actor representing the added labels. - """ - import numpy as np - - # Convert points to numpy array if needed - points_array = np.asarray(points) - - # Ensure points are 2D with shape (N, 3) - if points_array.ndim == 1: - points_array = points_array.reshape(-1, 3) - - # Create PyVista PolyData from points - point_cloud = pv.PolyData(points_array) - - # Add point labels to the scene - actor = self._pl.scene.add_point_labels( - point_cloud, - labels, - font_size=font_size, - point_size=point_size, - **kwargs - ) - - return actor - - def clear(self) -> None: - """Clear all actors from the scene. - - This method removes all previously added objects (meshes, points, lines, - text, etc.) from the visualization scene. - """ - self._pl.scene.clear() diff --git a/src/ansys/tools/visualization_interface/plotter.py b/src/ansys/tools/visualization_interface/plotter.py index 9af13d637..3dce0de52 100644 --- a/src/ansys/tools/visualization_interface/plotter.py +++ b/src/ansys/tools/visualization_interface/plotter.py @@ -458,157 +458,3 @@ def add_text( return self._backend.add_text( text=text, position=position, font_size=font_size, color=color, **kwargs ) - - def add_mesh( - self, - mesh: Any, - scalars: Optional[Union[str, Any]] = None, - scalar_bar_args: Optional[dict] = None, - show_edges: bool = False, - nan_color: str = "grey", - **kwargs - ) -> Any: - """Add a mesh to the scene. - - This method provides a backend-agnostic way to add mesh objects to the - visualization scene. Meshes can be colored by scalar values with - customizable color bars. - - Parameters - ---------- - mesh : Any - Mesh object to add. Can be a PyVista mesh (UnstructuredGrid, PolyData, - MultiBlock) or other backend-specific mesh type. - scalars : Optional[Union[str, Any]], default: None - Scalars to use for coloring. Can be a string name of an array in - the mesh, or an array-like object with scalar values. - scalar_bar_args : Optional[dict], default: None - Arguments for the scalar bar (colorbar). Common keys include: - - 'title': Title for the scalar bar - - 'vertical': Whether to orient vertically (default False) - show_edges : bool, default: False - Whether to show mesh edges. - nan_color : str, default: "grey" - Color to use for NaN values in scalars. - **kwargs : dict - Additional backend-specific keyword arguments for advanced customization - (e.g., cmap, opacity, lighting). - - Returns - ------- - Any - Backend-specific actor or object representing the added mesh. - Can be used for further manipulation or removal. - - Examples - -------- - Add a simple mesh: - - >>> from ansys.tools.visualization_interface import Plotter - >>> import pyvista as pv - >>> plotter = Plotter() - >>> mesh = pv.Sphere() - >>> plotter.add_mesh(mesh, show_edges=True) - >>> plotter.show() - - Add a mesh with scalar coloring: - - >>> mesh = pv.Sphere() - >>> mesh['elevation'] = mesh.points[:, 2] - >>> plotter.add_mesh( - ... mesh, - ... scalars='elevation', - ... scalar_bar_args={'title': 'Elevation (m)'}, - ... cmap='viridis' - ... ) - >>> plotter.show() - """ - return self._backend.add_mesh( - mesh=mesh, - scalars=scalars, - scalar_bar_args=scalar_bar_args, - show_edges=show_edges, - nan_color=nan_color, - **kwargs - ) - - def add_point_labels( - self, - points: Union[List, Any], - labels: List[str], - font_size: int = 12, - point_size: float = 5.0, - **kwargs - ) -> Any: - """Add labels at 3D point locations. - - This method provides a backend-agnostic way to add text labels at - specific 3D coordinates in the visualization scene. Labels are - displayed next to marker points. - - Parameters - ---------- - points : Union[List, Any] - Points where labels should be placed. Can be a list of coordinates - or array-like object. Expected format: [[x1, y1, z1], ...] or Nx3 array. - labels : List[str] - List of label strings to display at each point. Must have the same - length as points. - font_size : int, default: 12 - Font size for the labels. - point_size : float, default: 5.0 - Size of the point markers shown with labels. - **kwargs : dict - Additional backend-specific keyword arguments for advanced customization - (e.g., text_color, shape, fill_shape). - - Returns - ------- - Any - Backend-specific actor or object representing the added labels. - Can be used for further manipulation or removal. - - Examples - -------- - Add labels at specific locations: - - >>> from ansys.tools.visualization_interface import Plotter - >>> plotter = Plotter() - >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0]] - >>> labels = ['Origin', 'X-axis', 'Y-axis'] - >>> plotter.add_point_labels(points, labels, font_size=14) - >>> plotter.show() - - Add labels with custom styling: - - >>> plotter.add_point_labels( - ... points, - ... labels, - ... font_size=16, - ... point_size=10, - ... text_color='yellow', - ... shape='rounded_rect' - ... ) - >>> plotter.show() - """ - return self._backend.add_point_labels( - points=points, labels=labels, font_size=font_size, point_size=point_size, **kwargs - ) - - def clear(self) -> None: - """Clear all actors from the scene. - - This method removes all previously added objects (meshes, points, lines, - text, etc.) from the visualization scene. - - Examples - -------- - >>> from ansys.tools.visualization_interface import Plotter - >>> import pyvista as pv - >>> plotter = Plotter() - >>> plotter.add_mesh(pv.Sphere()) - >>> plotter.clear() # Remove all objects - >>> plotter.add_mesh(pv.Cube()) # Add different object - >>> plotter.show() - """ - self._backend.clear() From 918517516dcf5551d4d2a50ed078ffeef6c0e9bb Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Tue, 10 Feb 2026 12:54:22 +0100 Subject: [PATCH 08/31] test: Add tests for the public API --- tests/test_customization_api.py | 122 ++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 tests/test_customization_api.py diff --git a/tests/test_customization_api.py b/tests/test_customization_api.py new file mode 100644 index 000000000..5a137e37f --- /dev/null +++ b/tests/test_customization_api.py @@ -0,0 +1,122 @@ +# Copyright (C) 2024 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Test module for the customization API methods.""" + +from ansys.tools.visualization_interface import Plotter +from ansys.tools.visualization_interface.backends.plotly.plotly_interface import PlotlyBackend + + +def test_add_points_pyvista(): + """Test add_points API with PyVista backend.""" + pl = Plotter() + points = [[0, 0, 0], [1, 0, 0], [0, 1, 0]] + actor = pl.add_points(points, color="red", size=10.0) + assert actor is not None + pl.show() + + +def test_add_points_plotly(): + """Test add_points API with Plotly backend.""" + pl = Plotter(backend=PlotlyBackend()) + points = [[0, 0, 0], [1, 0, 0], [0, 1, 0]] + trace = pl.add_points(points, color="red", size=10.0) + assert trace is not None + + +def test_add_lines_pyvista(): + """Test add_lines API with PyVista backend.""" + pl = Plotter() + points = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]] + actor = pl.add_lines(points, color="blue", width=2.0) + assert actor is not None + pl.show() + + +def test_add_lines_plotly(): + """Test add_lines API with Plotly backend.""" + pl = Plotter(backend=PlotlyBackend()) + points = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]] + trace = pl.add_lines(points, color="blue", width=2.0) + assert trace is not None + + +def test_add_lines_with_connections_pyvista(): + """Test add_lines API with explicit connections using PyVista backend.""" + pl = Plotter() + points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]] + connections = [[0, 1], [2, 3]] + actor = pl.add_lines(points, connections=connections, color="green", width=2.0) + assert actor is not None + pl.show() + + +def test_add_lines_with_connections_plotly(): + """Test add_lines API with explicit connections using Plotly backend.""" + pl = Plotter(backend=PlotlyBackend()) + points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]] + connections = [[0, 1], [2, 3]] + trace = pl.add_lines(points, connections=connections, color="green", width=2.0) + assert trace is not None + + +def test_add_planes_pyvista(): + """Test add_planes API with PyVista backend.""" + pl = Plotter() + actor = pl.add_planes( + center=(0, 0, 0), + normal=(0, 0, 1), + i_size=2.0, + j_size=2.0, + color="lightblue", + opacity=0.5 + ) + assert actor is not None + pl.show() + + +def test_add_planes_plotly(): + """Test add_planes API with Plotly backend.""" + pl = Plotter(backend=PlotlyBackend()) + trace = pl.add_planes( + center=(0, 0, 0), + normal=(0, 0, 1), + i_size=2.0, + j_size=2.0, + color="lightblue", + opacity=0.5 + ) + assert trace is not None + + +def test_add_text_pyvista(): + """Test add_text API with PyVista backend.""" + pl = Plotter() + actor = pl.add_text("Test Label", position=(10, 10), font_size=14, color="yellow") + assert actor is not None + pl.show() + + +def test_add_text_plotly(): + """Test add_text API with Plotly backend.""" + pl = Plotter(backend=PlotlyBackend()) + annotation = pl.add_text("Test Label", position=(0, 0, 1), font_size=14, color="yellow") + assert annotation is not None From 26d450ea6aeb784cd1ea86ed87327a0cca7e7135 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 15:20:40 +0100 Subject: [PATCH 09/31] fix: Avoid using 3DText --- .../customization_api.py | 13 ++-- .../customization_api.py | 24 +++---- .../backends/plotly/plotly_interface.py | 63 +++++++----------- .../backends/pyvista/pyvista.py | 66 ++++--------------- .../tools/visualization_interface/plotter.py | 16 ++--- tests/test_customization_api.py | 14 +++- 6 files changed, 70 insertions(+), 126 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index 2154ff6ab..53181d920 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -95,15 +95,14 @@ ############################################################################### # Add text labels # ~~~~~~~~~~~~~~~ -# Add text annotations to label features. +# Add text annotations to label features using 2D screen coordinates. -# Axis labels (3D world coordinates) -plotter.add_text("X", position=(1.6, 0, 0), font_size=14, color='red') -plotter.add_text("Y", position=(0, 1.6, 0), font_size=14, color='green') -plotter.add_text("Z", position=(0, 0, 1.6), font_size=14, color='blue') +# Scene title at the top center +plotter.add_text("Customization API Example", position='upper_edge', font_size=18, color='white') -# Title (2D screen coordinates) -plotter.add_text("Customization API Example", position=(10, 10), font_size=18, color='white') +# Additional labels at the top corners +plotter.add_text("PyVista Backend", position='upper_left', font_size=12, color='lightblue') +plotter.add_text("3D Visualization", position='upper_right', font_size=12, color='lightgreen') ############################################################################### # Show the result diff --git a/examples/01-basic-plotly-examples/customization_api.py b/examples/01-basic-plotly-examples/customization_api.py index d0c9a5fc5..308cf4be9 100644 --- a/examples/01-basic-plotly-examples/customization_api.py +++ b/examples/01-basic-plotly-examples/customization_api.py @@ -48,7 +48,6 @@ sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) plotter.plot(sphere) -print("✓ Basic geometry added successfully with Plotly backend") ############################################################################### # Add points @@ -62,7 +61,6 @@ ] plotter.add_points(key_points, color='red', size=10) -print("✓ Points added successfully") ############################################################################### # Add lines @@ -81,7 +79,6 @@ z_axis = [[0, 0, 0], [0, 0, 1.5]] plotter.add_lines(z_axis, color='blue', width=4.0) -print("✓ Lines added successfully") ############################################################################### # Add a reference plane @@ -96,29 +93,24 @@ color='lightblue', opacity=0.2 ) -print("✓ Plane added successfully") ############################################################################### # Add text labels # ~~~~~~~~~~~~~~~ -# Add text annotations to label features. +# Add text annotations using 2D normalized coordinates (0-1 range). -# Axis labels (3D world coordinates) -plotter.add_text("X", position=(1.6, 0, 0), font_size=14, color='red') -plotter.add_text("Y", position=(0, 1.6, 0), font_size=14, color='green') -plotter.add_text("Z", position=(0, 0, 1.6), font_size=14, color='blue') -print("✓ Text labels added successfully") +# Scene title at the top center +plotter.add_text("Customization API Example", position=(0.5, 0.95), font_size=18, color='white') + +# Additional labels at the top corners +plotter.add_text("Plotly Backend", position=(0.05, 0.95), font_size=12, color='lightblue') +plotter.add_text("3D Visualization", position=(0.95, 0.95), font_size=12, color='lightgreen') ############################################################################### # Show the result # ~~~~~~~~~~~~~~~ # Display the visualization with all customizations. -print("\n" + "="*70) -print("Summary:") -print(" ✓ All customization APIs work with Plotly backend!") -print(" ✓ Same API calls as PyVista - truly backend-agnostic") -print("="*70) # Uncomment to show in browser: -plotter.show() +# plotter.show() diff --git a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py index aed20256a..0804045ad 100644 --- a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py +++ b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py @@ -435,52 +435,35 @@ def add_text(self, text, position, font_size=12, color="white", **kwargs): ---------- text : str Text string to display. - position : Union[Tuple[float, float], Tuple[float, float, float]] - Position for the text. For 3D, provide (x, y, z) coordinates. - For 2D annotations, this will be interpreted as 3D coordinates. + position : Tuple[float, float] + Position for the text as 2D screen coordinates (x, y). + Values should be between 0 and 1 for normalized coordinates, + or pixel values for absolute positioning. font_size : int, default: 12 Font size for the text. color : str, default: "white" Color of the text. **kwargs : dict - Additional keyword arguments passed to Plotly's Scatter3d or annotation. + Additional keyword arguments passed to Plotly's annotation. Returns ------- - Union[go.Scatter3d, dict] - Plotly trace or annotation representing the added text. + dict + Plotly annotation representing the added text. """ - # For Plotly, we'll use Scatter3d with text mode for 3D text - if len(position) == 3: - # 3D text using scatter points with text - text_trace = go.Scatter3d( - x=[position[0]], - y=[position[1]], - z=[position[2]], - mode='text', - text=[text], - textfont=dict( - size=font_size, - color=color, - ), - **kwargs - ) - self._fig.add_trace(text_trace) - return text_trace - else: - # 2D annotation - annotation = dict( - x=position[0] if len(position) > 0 else 0, - y=position[1] if len(position) > 1 else 0, - text=text, - font=dict( - size=font_size, - color=color, - ), - showarrow=False, - xref="paper", - yref="paper", - **kwargs - ) - self._fig.add_annotation(annotation) - return annotation + # 2D annotation with normalized coordinates + annotation = dict( + x=position[0] if len(position) > 0 else 0, + y=position[1] if len(position) > 1 else 0, + text=text, + font=dict( + size=font_size, + color=color, + ), + showarrow=False, + xref="paper", + yref="paper", + **kwargs + ) + self._fig.add_annotation(annotation) + return annotation diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py index 5a810180f..a36ca7405 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py @@ -912,7 +912,7 @@ def add_planes( def add_text( self, text: str, - position: Union[tuple[float, float], tuple[float, float, float]], + position: Union[tuple[float, float], str], font_size: int = 12, color: str = "white", **kwargs @@ -923,70 +923,32 @@ def add_text( ---------- text : str Text string to display. - position : Union[Tuple[float, float], Tuple[float, float, float], str] + position : Union[Tuple[float, float], str] Position for the text. Can be: - - 2D tuple (x, y) for screen coordinates - - 3D tuple (x, y, z) for world coordinates + + - 2D tuple (x, y) for screen coordinates (pixels from bottom-left) - String position like 'upper_left', 'upper_right', 'lower_left', 'lower_right', 'upper_edge', 'lower_edge' (PyVista-specific) + font_size : int, default: 12 Font size for the text. color : str, default: "white" Color of the text. **kwargs : dict - Additional keyword arguments passed to PyVista's add_text or - add_point_labels method. + Additional keyword arguments passed to PyVista's add_text method. Returns ------- pv.Actor PyVista actor representing the added text. """ - # Handle string positions (PyVista-specific) - if isinstance(position, str): - actor = self._pl.scene.add_text( - text, - position=position, - font_size=font_size, - color=color, - **kwargs - ) - return actor - - # Determine if position is 2D or 3D - if len(position) == 2: - # 2D screen coordinates - use add_text - actor = self._pl.scene.add_text( - text, - position=position, - font_size=font_size, - color=color, - **kwargs - ) - elif len(position) == 3: - # 3D world coordinates - create 3D text mesh - # We use Text3D instead of add_point_labels to avoid a bug in PyVista - # where it tries to access a non-existent _actors attribute - - # Create 3D text object - text_mesh = pv.Text3D(text, depth=0.0) - - # Scale text based on font_size (approximate scaling factor) - scale_factor = font_size / 100.0 - text_mesh.points *= scale_factor - - # Translate text to desired position - text_mesh.translate(position, inplace=True) - - # Add text mesh to scene with specified color - actor = self._pl.scene.add_mesh( - text_mesh, - color=color, - **kwargs - ) - else: - raise ValueError( - f"Position must be 2D (x, y) or 3D (x, y, z), got {len(position)} dimensions" - ) + # Handle string positions or 2D coordinates + actor = self._pl.scene.add_text( + text, + position=position, + font_size=font_size, + color=color, + **kwargs + ) return actor diff --git a/src/ansys/tools/visualization_interface/plotter.py b/src/ansys/tools/visualization_interface/plotter.py index 3dce0de52..14dac59c1 100644 --- a/src/ansys/tools/visualization_interface/plotter.py +++ b/src/ansys/tools/visualization_interface/plotter.py @@ -401,7 +401,7 @@ def add_planes( def add_text( self, text: str, - position: Union[Tuple[float, float], Tuple[float, float, float], str], + position: Union[Tuple[float, float], str], font_size: int = 12, color: str = "white", **kwargs @@ -409,19 +409,19 @@ def add_text( """Add text to the scene. This method provides a backend-agnostic way to add text labels to the - visualization scene. Text can be positioned in 2D screen coordinates or - 3D world coordinates depending on the backend capabilities. + visualization scene. Text is positioned using 2D screen coordinates. Parameters ---------- text : str Text string to display. - position : Union[Tuple[float, float], Tuple[float, float, float], str] + position : Union[Tuple[float, float], str] Position for the text. Can be: + - 2D tuple (x, y) for screen/viewport coordinates (pixels from bottom-left) - - 3D tuple (x, y, z) for world coordinates (backend-dependent support) - String position like 'upper_left', 'upper_right', 'lower_left', 'lower_right', 'upper_edge', 'lower_edge' (backend-dependent support) + font_size : int, default: 12 Font size for the text in points. color : str, default: "white" @@ -445,11 +445,11 @@ def add_text( >>> plotter.add_text("Title", position=(10, 10), font_size=18, color='yellow') >>> plotter.show() - Add text at a 3D world coordinate: + Add text using a named position: >>> plotter.add_text( - ... "Point A", - ... position=(1.0, 2.0, 3.0), + ... "Corner Label", + ... position='upper_right', ... font_size=14, ... color='red' ... ) diff --git a/tests/test_customization_api.py b/tests/test_customization_api.py index 5a137e37f..697356fcb 100644 --- a/tests/test_customization_api.py +++ b/tests/test_customization_api.py @@ -108,15 +108,23 @@ def test_add_planes_plotly(): def test_add_text_pyvista(): - """Test add_text API with PyVista backend.""" + """Test add_text API with PyVista backend using 2D screen coordinates.""" pl = Plotter() actor = pl.add_text("Test Label", position=(10, 10), font_size=14, color="yellow") assert actor is not None pl.show() +def test_add_text_pyvista_string_position(): + """Test add_text API with PyVista backend using string position.""" + pl = Plotter() + actor = pl.add_text("Test Label", position='upper_left', font_size=14, color="yellow") + assert actor is not None + pl.show() + + def test_add_text_plotly(): - """Test add_text API with Plotly backend.""" + """Test add_text API with Plotly backend using 2D normalized coordinates.""" pl = Plotter(backend=PlotlyBackend()) - annotation = pl.add_text("Test Label", position=(0, 0, 1), font_size=14, color="yellow") + annotation = pl.add_text("Test Label", position=(0.5, 0.9), font_size=14, color="yellow") assert annotation is not None From 1ab9bb73ee7130fd80d13d66f56ab9c37e16ed3b Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 15:38:09 +0100 Subject: [PATCH 10/31] fix: Fixes and reset image cache --- .github/workflows/ci_cd.yml | 2 +- tests/test_generic_plotter.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index a4fb27459..6a57fbc7f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -11,7 +11,7 @@ on: env: MAIN_PYTHON_VERSION: '3.13' - RESET_IMAGE_CACHE: 1 + RESET_IMAGE_CACHE: 2 PACKAGE_NAME: ansys-tools-visualization-interface DOCUMENTATION_CNAME: visualization-interface.tools.docs.pyansys.com IN_GITHUB_ACTIONS: true diff --git a/tests/test_generic_plotter.py b/tests/test_generic_plotter.py index a0138c5e0..04d83b739 100644 --- a/tests/test_generic_plotter.py +++ b/tests/test_generic_plotter.py @@ -43,19 +43,21 @@ def __init__(self, name) -> None: @pytest.fixture(autouse=True) -def wrapped_verify_image_cache(verify_image_cache): - """Wraps the verify_image_cache fixture to ensure that the image cache is verified. +def skip_image_cache(verify_image_cache): + """Configure verify_image_cache to allow high variance tests. Parameters ---------- verify_image_cache : fixture - Fixture to wrap. + Fixture from pytest-pyvista for image verification. Returns ------- fixture - Wrapped fixture. + Configured fixture. """ + verify_image_cache.high_variance_test = True + verify_image_cache.windows_skip_image_cache = True return verify_image_cache From e8f977aa4982fcad2828b01a5f3f2add833f634e Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:39:28 +0000 Subject: [PATCH 11/31] chore: adding changelog file 465.maintenance.md [dependabot-skip] --- doc/changelog.d/{465.miscellaneous.md => 465.maintenance.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changelog.d/{465.miscellaneous.md => 465.maintenance.md} (100%) diff --git a/doc/changelog.d/465.miscellaneous.md b/doc/changelog.d/465.maintenance.md similarity index 100% rename from doc/changelog.d/465.miscellaneous.md rename to doc/changelog.d/465.maintenance.md From 586308aeb2ed6ab62da0d45bab61e2d0abf0055b Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 15:45:29 +0100 Subject: [PATCH 12/31] fix: add text --- .../00-basic-pyvista-examples/customization_api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index 53181d920..e607247c8 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -97,12 +97,12 @@ # ~~~~~~~~~~~~~~~ # Add text annotations to label features using 2D screen coordinates. -# Scene title at the top center -plotter.add_text("Customization API Example", position='upper_edge', font_size=18, color='white') +# Scene title at the top (pixel coordinates) +plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') -# Additional labels at the top corners -plotter.add_text("PyVista Backend", position='upper_left', font_size=12, color='lightblue') -plotter.add_text("3D Visualization", position='upper_right', font_size=12, color='lightgreen') +# Additional labels at corners (pixel coordinates) +plotter.add_text("PyVista Backend", position=(10, 550), font_size=12, color='lightblue') +plotter.add_text("3D Visualization", position=(800, 550), font_size=12, color='lightgreen') ############################################################################### # Show the result From 45e93621b4dca2023d5fdf4f2499cf19cabcfe12 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 15:54:57 +0100 Subject: [PATCH 13/31] fix: Revert --- tests/test_generic_plotter.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_generic_plotter.py b/tests/test_generic_plotter.py index 04d83b739..a0138c5e0 100644 --- a/tests/test_generic_plotter.py +++ b/tests/test_generic_plotter.py @@ -43,21 +43,19 @@ def __init__(self, name) -> None: @pytest.fixture(autouse=True) -def skip_image_cache(verify_image_cache): - """Configure verify_image_cache to allow high variance tests. +def wrapped_verify_image_cache(verify_image_cache): + """Wraps the verify_image_cache fixture to ensure that the image cache is verified. Parameters ---------- verify_image_cache : fixture - Fixture from pytest-pyvista for image verification. + Fixture to wrap. Returns ------- fixture - Configured fixture. + Wrapped fixture. """ - verify_image_cache.high_variance_test = True - verify_image_cache.windows_skip_image_cache = True return verify_image_cache From 9673fe30daaa133b3acabbe7319e2eaf46ea2be7 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 16:05:28 +0100 Subject: [PATCH 14/31] debug --- .../00-basic-pyvista-examples/customization_api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index e607247c8..faa0d31a9 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -97,12 +97,12 @@ # ~~~~~~~~~~~~~~~ # Add text annotations to label features using 2D screen coordinates. -# Scene title at the top (pixel coordinates) -plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') +# # Scene title at the top (pixel coordinates) +# plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') -# Additional labels at corners (pixel coordinates) -plotter.add_text("PyVista Backend", position=(10, 550), font_size=12, color='lightblue') -plotter.add_text("3D Visualization", position=(800, 550), font_size=12, color='lightgreen') +# # Additional labels at corners (pixel coordinates) +# plotter.add_text("PyVista Backend", position=(10, 550), font_size=12, color='lightblue') +# plotter.add_text("3D Visualization", position=(800, 550), font_size=12, color='lightgreen') ############################################################################### # Show the result From f7c3b0a794494a82aec5bedd42d1b8ffd54127ff Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 16:19:14 +0100 Subject: [PATCH 15/31] debug --- .../00-basic-pyvista-examples/customization_api.py | 10 +++++----- .../backends/pyvista/pyvista_interface.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index faa0d31a9..e607247c8 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -97,12 +97,12 @@ # ~~~~~~~~~~~~~~~ # Add text annotations to label features using 2D screen coordinates. -# # Scene title at the top (pixel coordinates) -# plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') +# Scene title at the top (pixel coordinates) +plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') -# # Additional labels at corners (pixel coordinates) -# plotter.add_text("PyVista Backend", position=(10, 550), font_size=12, color='lightblue') -# plotter.add_text("3D Visualization", position=(800, 550), font_size=12, color='lightgreen') +# Additional labels at corners (pixel coordinates) +plotter.add_text("PyVista Backend", position=(10, 550), font_size=12, color='lightblue') +plotter.add_text("3D Visualization", position=(800, 550), font_size=12, color='lightgreen') ############################################################################### # Show the result diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py index 6b520451f..a1c17a83b 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py @@ -460,8 +460,9 @@ def show( self.scene.off_screen = True # In off-screen mode (e.g., sphinx-gallery), prevent auto-close - # so the plotter can be reused - if self.scene.off_screen and 'auto_close' not in kwargs: + # so the plotter can be reused. However, in test mode, we need + # to allow auto-close so pytest-pyvista can capture images properly + if self.scene.off_screen and 'auto_close' not in kwargs and not viz_interface.TESTING_MODE: kwargs['auto_close'] = False if jupyter_backend: From 9a4d2d4bffac0d09b5ee9385b31c0cb22be64a20 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 16:34:12 +0100 Subject: [PATCH 16/31] debug --- .../backends/pyvista/pyvista_interface.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py index a1c17a83b..8090a3bd2 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py @@ -459,12 +459,6 @@ def show( if kwargs.get("screenshot") is not None: self.scene.off_screen = True - # In off-screen mode (e.g., sphinx-gallery), prevent auto-close - # so the plotter can be reused. However, in test mode, we need - # to allow auto-close so pytest-pyvista can capture images properly - if self.scene.off_screen and 'auto_close' not in kwargs and not viz_interface.TESTING_MODE: - kwargs['auto_close'] = False - if jupyter_backend: # Remove jupyter_backend from show options since we pass it manually kwargs.pop("jupyter_backend", None) From a926f7022bd25f6cfb0c2cf3b4cd133bfcaaeb87 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Wed, 11 Feb 2026 16:49:23 +0100 Subject: [PATCH 17/31] debug --- examples/00-basic-pyvista-examples/customization_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index e607247c8..5bfd4b571 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -101,8 +101,8 @@ plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') # Additional labels at corners (pixel coordinates) -plotter.add_text("PyVista Backend", position=(10, 550), font_size=12, color='lightblue') -plotter.add_text("3D Visualization", position=(800, 550), font_size=12, color='lightgreen') +plotter.add_text("PyVista Backend", position=(1, 1), font_size=12, color='lightblue') +plotter.add_text("3D Visualization", position=(1, 10), font_size=12, color='lightgreen') ############################################################################### # Show the result From 4decc89e346c89b6ee7fc6c6c4f64a78dc1083f7 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Thu, 12 Feb 2026 10:44:34 +0100 Subject: [PATCH 18/31] debug: Remove doc dep, comment example except show --- .github/workflows/ci_cd.yml | 2 +- .../customization_api.py | 118 +++++++++--------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 6a57fbc7f..a24282357 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -83,7 +83,7 @@ jobs: docs-build: name: Documentation Build runs-on: ubuntu-latest - needs: [docs-style] + # needs: [docs-style] steps: - name: Setup headless display uses: pyvista/setup-headless-display-action@7d84ae825e6d9297a8e99bdbbae20d1b919a0b19 # v4.2 diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index 5bfd4b571..c6cd06f82 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -44,65 +44,65 @@ plotter = Plotter() -# Add a sphere as our main geometry -sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) -plotter.plot(sphere, color='lightblue', opacity=0.6) - -############################################################################### -# Add points -# ~~~~~~~~~~ -# Add point markers to highlight specific locations. - -key_points = [ - [1, 0, 0], # Point on X axis - [0, 1, 0], # Point on Y axis - [0, 0, 1], # Point on Z axis -] - -plotter.add_points(key_points, color='red', size=20) - -############################################################################### -# Add lines -# ~~~~~~~~~ -# Add line segments to show coordinate axes or connections. - -# X axis -x_axis = [[0, 0, 0], [1.5, 0, 0]] -plotter.add_lines(x_axis, color='red', width=4.0) - -# Y axis -y_axis = [[0, 0, 0], [0, 1.5, 0]] -plotter.add_lines(y_axis, color='green', width=4.0) - -# Z axis -z_axis = [[0, 0, 0], [0, 0, 1.5]] -plotter.add_lines(z_axis, color='blue', width=4.0) - -############################################################################### -# Add a reference plane -# ~~~~~~~~~~~~~~~~~~~~~ -# Add a plane to show a reference surface. - -plotter.add_planes( - center=(0, 0, 0), - normal=(0, 0, 1), - i_size=2.5, - j_size=2.5, - color='white', - opacity=0.2 -) - -############################################################################### -# Add text labels -# ~~~~~~~~~~~~~~~ -# Add text annotations to label features using 2D screen coordinates. - -# Scene title at the top (pixel coordinates) -plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') - -# Additional labels at corners (pixel coordinates) -plotter.add_text("PyVista Backend", position=(1, 1), font_size=12, color='lightblue') -plotter.add_text("3D Visualization", position=(1, 10), font_size=12, color='lightgreen') +# # Add a sphere as our main geometry +# sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) +# plotter.plot(sphere, color='lightblue', opacity=0.6) + +# ############################################################################### +# # Add points +# # ~~~~~~~~~~ +# # Add point markers to highlight specific locations. + +# key_points = [ +# [1, 0, 0], # Point on X axis +# [0, 1, 0], # Point on Y axis +# [0, 0, 1], # Point on Z axis +# ] + +# plotter.add_points(key_points, color='red', size=20) + +# ############################################################################### +# # Add lines +# # ~~~~~~~~~ +# # Add line segments to show coordinate axes or connections. + +# # X axis +# x_axis = [[0, 0, 0], [1.5, 0, 0]] +# plotter.add_lines(x_axis, color='red', width=4.0) + +# # Y axis +# y_axis = [[0, 0, 0], [0, 1.5, 0]] +# plotter.add_lines(y_axis, color='green', width=4.0) + +# # Z axis +# z_axis = [[0, 0, 0], [0, 0, 1.5]] +# plotter.add_lines(z_axis, color='blue', width=4.0) + +# ############################################################################### +# # Add a reference plane +# # ~~~~~~~~~~~~~~~~~~~~~ +# # Add a plane to show a reference surface. + +# plotter.add_planes( +# center=(0, 0, 0), +# normal=(0, 0, 1), +# i_size=2.5, +# j_size=2.5, +# color='white', +# opacity=0.2 +# ) + +# ############################################################################### +# # Add text labels +# # ~~~~~~~~~~~~~~~ +# # Add text annotations to label features using 2D screen coordinates. + +# # Scene title at the top (pixel coordinates) +# plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') + +# # Additional labels at corners (pixel coordinates) +# plotter.add_text("PyVista Backend", position=(1, 1), font_size=12, color='lightblue') +# plotter.add_text("3D Visualization", position=(1, 10), font_size=12, color='lightgreen') ############################################################################### # Show the result From 2279c8de52269794a6fceb6c148fb5781a271f7b Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Thu, 12 Feb 2026 10:48:04 +0100 Subject: [PATCH 19/31] debug: remove example --- .../customization_api.py | 112 ------------------ 1 file changed, 112 deletions(-) delete mode 100644 examples/00-basic-pyvista-examples/customization_api.py diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py deleted file mode 100644 index c6cd06f82..000000000 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (C) 2024 - 2026 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _customization_api_example: - -Backend-Agnostic Customization APIs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This example demonstrates the new backend-agnostic customization APIs -that allow you to add points, lines, planes, and text to visualizations -without directly coupling to the PyVista backend. - -These APIs work consistently across different backends, making your code -more portable and maintainable. -""" - -import pyvista as pv -from ansys.tools.visualization_interface import Plotter - -############################################################################### -# Create a plotter and add basic geometry -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# First, create a plotter and add some geometry to visualize. - -plotter = Plotter() - -# # Add a sphere as our main geometry -# sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) -# plotter.plot(sphere, color='lightblue', opacity=0.6) - -# ############################################################################### -# # Add points -# # ~~~~~~~~~~ -# # Add point markers to highlight specific locations. - -# key_points = [ -# [1, 0, 0], # Point on X axis -# [0, 1, 0], # Point on Y axis -# [0, 0, 1], # Point on Z axis -# ] - -# plotter.add_points(key_points, color='red', size=20) - -# ############################################################################### -# # Add lines -# # ~~~~~~~~~ -# # Add line segments to show coordinate axes or connections. - -# # X axis -# x_axis = [[0, 0, 0], [1.5, 0, 0]] -# plotter.add_lines(x_axis, color='red', width=4.0) - -# # Y axis -# y_axis = [[0, 0, 0], [0, 1.5, 0]] -# plotter.add_lines(y_axis, color='green', width=4.0) - -# # Z axis -# z_axis = [[0, 0, 0], [0, 0, 1.5]] -# plotter.add_lines(z_axis, color='blue', width=4.0) - -# ############################################################################### -# # Add a reference plane -# # ~~~~~~~~~~~~~~~~~~~~~ -# # Add a plane to show a reference surface. - -# plotter.add_planes( -# center=(0, 0, 0), -# normal=(0, 0, 1), -# i_size=2.5, -# j_size=2.5, -# color='white', -# opacity=0.2 -# ) - -# ############################################################################### -# # Add text labels -# # ~~~~~~~~~~~~~~~ -# # Add text annotations to label features using 2D screen coordinates. - -# # Scene title at the top (pixel coordinates) -# plotter.add_text("Customization API Example", position=(400, 550), font_size=18, color='white') - -# # Additional labels at corners (pixel coordinates) -# plotter.add_text("PyVista Backend", position=(1, 1), font_size=12, color='lightblue') -# plotter.add_text("3D Visualization", position=(1, 10), font_size=12, color='lightgreen') - -############################################################################### -# Show the result -# ~~~~~~~~~~~~~~~ -# Display the visualization with all customizations. - -plotter.show() From d2393f2ad307bbb8f0b03fe6baf6a70b5528474e Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Thu, 12 Feb 2026 11:33:49 +0100 Subject: [PATCH 20/31] fix: Example --- .../customization_api.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/00-basic-pyvista-examples/customization_api.py diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py new file mode 100644 index 000000000..fd693887f --- /dev/null +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -0,0 +1,60 @@ +# Copyright (C) 2024 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _api_ex: + +Customization API example +========================= + +This example demonstrates how to use the customization API of the visualization interface +to add various elements to a PyVista scene, such as points, lines, planes, and text annotations. +The example also shows how to plot a simple sphere mesh and customize its appearance. + +""" + + +from ansys.tools.visualization_interface import Plotter +import pyvista as pv + +sphere = pv.Sphere() +pl = Plotter() + +# Add points at specific locations +points = [[0, 0, 0], [1, 1, 1], [2, 2, 2]] +pl.add_points(points, color="yellow", size=15) + +# Add lines connecting the points +lines = [[0, 1], [1, 2]] +pl.add_lines(points, lines, color="red", width=5) + +# Add a plane - note: add_planes takes a single center and normal, not lists +pl.add_planes(center=(0, 0, 0), normal=(0, 0, 1), i_size=2.0, j_size=2.0, color="blue", opacity=0.5) + +# Add text annotation to the scene +pl.add_text("Customization API Example", position="upper_left", font_size=16, color="white") + +# Plot the sphere mesh +pl.plot(sphere, color="lightblue", opacity=0.8) + +# Show the complete visualization +pl.show() \ No newline at end of file From 1a5464f195a468774f8c8a4bf8491345d761efbe Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Thu, 12 Feb 2026 11:37:52 +0100 Subject: [PATCH 21/31] fix: Improve example --- .../customization_api.py | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index fd693887f..bd9c62510 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -28,10 +28,12 @@ This example demonstrates how to use the customization API of the visualization interface to add various elements to a PyVista scene, such as points, lines, planes, and text annotations. -The example also shows how to plot a simple sphere mesh and customize its appearance. """ +############################################################################### +# Create the base plotter and mesh +# ================================= from ansys.tools.visualization_interface import Plotter import pyvista as pv @@ -39,22 +41,38 @@ sphere = pv.Sphere() pl = Plotter() -# Add points at specific locations +############################################################################### +# Add points to mark specific locations +# ====================================== +# Use add_points to add colored markers at specific 3D coordinates + points = [[0, 0, 0], [1, 1, 1], [2, 2, 2]] pl.add_points(points, color="yellow", size=15) -# Add lines connecting the points +############################################################################### +# Add lines connecting points +# =========================== +# Use add_lines to draw line segments connecting points + lines = [[0, 1], [1, 2]] pl.add_lines(points, lines, color="red", width=5) -# Add a plane - note: add_planes takes a single center and normal, not lists +############################################################################### +# Add a reference plane +# ===================== +# Use add_planes to add a semi-transparent plane for reference + pl.add_planes(center=(0, 0, 0), normal=(0, 0, 1), i_size=2.0, j_size=2.0, color="blue", opacity=0.5) -# Add text annotation to the scene -pl.add_text("Customization API Example", position="upper_left", font_size=16, color="white") +############################################################################### +# Plot the main geometry +# ====================== +# Add the sphere mesh with custom appearance -# Plot the sphere mesh pl.plot(sphere, color="lightblue", opacity=0.8) -# Show the complete visualization +############################################################################### +# Display the complete visualization +# =================================== + pl.show() \ No newline at end of file From 0c7b7fc43bc7dca6cbb52fe58cd88d692158da69 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Thu, 12 Feb 2026 13:07:40 +0100 Subject: [PATCH 22/31] fix: Example --- .../customization_api.py | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index bd9c62510..b8758f765 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -28,12 +28,10 @@ This example demonstrates how to use the customization API of the visualization interface to add various elements to a PyVista scene, such as points, lines, planes, and text annotations. +The example also shows how to plot a simple sphere mesh and customize its appearance. """ -############################################################################### -# Create the base plotter and mesh -# ================================= from ansys.tools.visualization_interface import Plotter import pyvista as pv @@ -41,38 +39,19 @@ sphere = pv.Sphere() pl = Plotter() -############################################################################### -# Add points to mark specific locations -# ====================================== -# Use add_points to add colored markers at specific 3D coordinates - +# Add points at specific locations points = [[0, 0, 0], [1, 1, 1], [2, 2, 2]] pl.add_points(points, color="yellow", size=15) -############################################################################### -# Add lines connecting points -# =========================== -# Use add_lines to draw line segments connecting points - +# Add lines connecting the points lines = [[0, 1], [1, 2]] pl.add_lines(points, lines, color="red", width=5) -############################################################################### -# Add a reference plane -# ===================== -# Use add_planes to add a semi-transparent plane for reference - +# Add a plane - note: add_planes takes a single center and normal, not lists pl.add_planes(center=(0, 0, 0), normal=(0, 0, 1), i_size=2.0, j_size=2.0, color="blue", opacity=0.5) -############################################################################### -# Plot the main geometry -# ====================== -# Add the sphere mesh with custom appearance - +# Plot the sphere mesh pl.plot(sphere, color="lightblue", opacity=0.8) -############################################################################### -# Display the complete visualization -# =================================== - +# Show the complete visualization pl.show() \ No newline at end of file From 5b6e175115bdff5483c757f1f10e0f49e70cd8e7 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Date: Thu, 12 Feb 2026 13:15:00 +0100 Subject: [PATCH 23/31] Update ci_cd.yml --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index a24282357..6a57fbc7f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -83,7 +83,7 @@ jobs: docs-build: name: Documentation Build runs-on: ubuntu-latest - # needs: [docs-style] + needs: [docs-style] steps: - name: Setup headless display uses: pyvista/setup-headless-display-action@7d84ae825e6d9297a8e99bdbbae20d1b919a0b19 # v4.2 From 1f861e595b8f1560cda66e5883bef67b79ea9f05 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Thu, 12 Feb 2026 14:15:50 +0100 Subject: [PATCH 24/31] fix: Example titles --- .../01-basic-plotly-examples/customization_api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/01-basic-plotly-examples/customization_api.py b/examples/01-basic-plotly-examples/customization_api.py index 308cf4be9..625751ab7 100644 --- a/examples/01-basic-plotly-examples/customization_api.py +++ b/examples/01-basic-plotly-examples/customization_api.py @@ -37,7 +37,7 @@ from ansys.tools.visualization_interface import Plotter from ansys.tools.visualization_interface.backends.plotly.plotly_interface import PlotlyBackend -############################################################################### +###################################### # Create a plotter with Plotly backend # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create a plotter using the Plotly backend and add basic geometry. @@ -49,7 +49,7 @@ plotter.plot(sphere) -############################################################################### +############ # Add points # ~~~~~~~~~~ # Add point markers to highlight specific locations. @@ -62,7 +62,7 @@ plotter.add_points(key_points, color='red', size=10) -############################################################################### +########### # Add lines # ~~~~~~~~~ # Add line segments to show coordinate axes. @@ -80,7 +80,7 @@ plotter.add_lines(z_axis, color='blue', width=4.0) -############################################################################### +####################### # Add a reference plane # ~~~~~~~~~~~~~~~~~~~~~ # Add a plane to show a reference surface. @@ -94,7 +94,7 @@ opacity=0.2 ) -############################################################################### +################# # Add text labels # ~~~~~~~~~~~~~~~ # Add text annotations using 2D normalized coordinates (0-1 range). @@ -106,7 +106,7 @@ plotter.add_text("Plotly Backend", position=(0.05, 0.95), font_size=12, color='lightblue') plotter.add_text("3D Visualization", position=(0.95, 0.95), font_size=12, color='lightgreen') -############################################################################### +################# # Show the result # ~~~~~~~~~~~~~~~ # Display the visualization with all customizations. From 8837bf800aa930d49e3e0df779099b10c3a20b55 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 13 Feb 2026 11:19:39 +0100 Subject: [PATCH 25/31] fix: Add sections --- doc/source/conf.py | 2 + .../00-basic-pyvista-examples/animation.py | 8 +- .../customization_api.py | 84 +++++++++++++++---- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 4d50625c8..36bcd4b8a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -117,6 +117,8 @@ "thumbnail_size": (350, 350), "remove_config_comments": True, "show_signature": False, + # Prevent module resets which can close plotters between code blocks + "reset_modules": (), } diff --git a/examples/00-basic-pyvista-examples/animation.py b/examples/00-basic-pyvista-examples/animation.py index 2ce92bace..bdfe21df7 100644 --- a/examples/00-basic-pyvista-examples/animation.py +++ b/examples/00-basic-pyvista-examples/animation.py @@ -36,9 +36,9 @@ from ansys.tools.visualization_interface import Plotter -############################################################################### +############################## # Create sample animation data -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Generate a series of meshes representing a wave propagation over time. def create_wave_mesh(time_step, n_points=50): @@ -62,7 +62,7 @@ def create_wave_mesh(time_step, n_points=50): # Create 30 frames frames = [create_wave_mesh(i) for i in range(30)] -############################################################################### +############################################## # Display animation with interactive controls # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create and show an animation with play/pause, stop, and frame navigation. @@ -78,7 +78,7 @@ def create_wave_mesh(time_step, n_points=50): # Display with interactive controls animation.show() -############################################################################### +###################### # Interactive Controls # ~~~~~~~~~~~~~~~~~~~~ # The animation window includes the following controls: diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index b8758f765..724750095 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -36,22 +36,78 @@ from ansys.tools.visualization_interface import Plotter import pyvista as pv -sphere = pv.Sphere() -pl = Plotter() +###################################### +# Create a plotter with Plotly backend +# ==================================== +# Create a plotter using the Plotly backend and add basic geometry. -# Add points at specific locations -points = [[0, 0, 0], [1, 1, 1], [2, 2, 2]] -pl.add_points(points, color="yellow", size=15) +plotter = Plotter() -# Add lines connecting the points -lines = [[0, 1], [1, 2]] -pl.add_lines(points, lines, color="red", width=5) +# Add a sphere - this works fine +sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) +plotter.plot(sphere) -# Add a plane - note: add_planes takes a single center and normal, not lists -pl.add_planes(center=(0, 0, 0), normal=(0, 0, 1), i_size=2.0, j_size=2.0, color="blue", opacity=0.5) -# Plot the sphere mesh -pl.plot(sphere, color="lightblue", opacity=0.8) +############ +# Add points +# ========== +# Add point markers to highlight specific locations. -# Show the complete visualization -pl.show() \ No newline at end of file +key_points = [ + [1, 0, 0], # Point on X axis + [0, 1, 0], # Point on Y axis + [0, 0, 1], # Point on Z axis +] + +plotter.add_points(key_points, color='red', size=10) + +########### +# Add lines +# ========= +# Add line segments to show coordinate axes. + +# X axis +x_axis = [[0, 0, 0], [1.5, 0, 0]] +plotter.add_lines(x_axis, color='red', width=4.0) + +# Y axis +y_axis = [[0, 0, 0], [0, 1.5, 0]] +plotter.add_lines(y_axis, color='green', width=4.0) + +# Z axis +z_axis = [[0, 0, 0], [0, 0, 1.5]] +plotter.add_lines(z_axis, color='blue', width=4.0) + + +####################### +# Add a reference plane +# ===================== +# Add a plane to show a reference surface. + +plotter.add_planes( + center=(0, 0, 0), + normal=(0, 0, 1), + i_size=2.5, + j_size=2.5, + color='lightblue', + opacity=0.2 +) + +################# +# Add text labels +# =============== +# Add text annotations using 2D normalized coordinates (0-1 range). + +# Scene title at the top center +plotter.add_text("Customization API Example", position=(0.5, 0.95), font_size=18, color='white') + +# Additional labels at the top corners +plotter.add_text("Plotly Backend", position=(0.05, 0.95), font_size=12, color='lightblue') +plotter.add_text("3D Visualization", position=(0.95, 0.95), font_size=12, color='lightgreen') + +################# +# Show the result +# =============== +# Display the visualization with all customizations. + +plotter.show() From a8ef11a398472cb8dd9af087b7408f07cd062c39 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:20:45 +0000 Subject: [PATCH 26/31] chore: adding changelog file 465.added.md [dependabot-skip] --- doc/changelog.d/{465.maintenance.md => 465.added.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changelog.d/{465.maintenance.md => 465.added.md} (100%) diff --git a/doc/changelog.d/465.maintenance.md b/doc/changelog.d/465.added.md similarity index 100% rename from doc/changelog.d/465.maintenance.md rename to doc/changelog.d/465.added.md From a966121016537447f92d36e89b07f5bceff61acd Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 13 Feb 2026 15:36:18 +0100 Subject: [PATCH 27/31] fix: Typehints --- .../backends/plotly/plotly_interface.py | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py index 0804045ad..6cd07478f 100644 --- a/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py +++ b/src/ansys/tools/visualization_interface/backends/plotly/plotly_interface.py @@ -21,7 +21,7 @@ # SOFTWARE. """Plotly backend interface for visualization.""" -from typing import Any, Iterable, Union +from typing import Any, Iterable, List, Optional, Tuple, Union import plotly.graph_objects as go import pyvista as pv @@ -241,7 +241,13 @@ def show(self, else: self._fig.write_image(screenshot_str) - def add_points(self, points, color="red", size=10.0, **kwargs): + def add_points( + self, + points: Union[List, Any], + color: str = "red", + size: float = 10.0, + **kwargs + ) -> Any: """Add point markers to the scene. Parameters @@ -283,7 +289,14 @@ def add_points(self, points, color="red", size=10.0, **kwargs): self._fig.add_trace(scatter) return scatter - def add_lines(self, points, connections=None, color="white", width=1.0, **kwargs): + def add_lines( + self, + points: Union[List, Any], + connections: Optional[Union[List, Any]] = None, + color: str = "white", + width: float = 1.0, + **kwargs + ) -> Any: """Add line segments to the scene. Parameters @@ -350,7 +363,14 @@ def add_lines(self, points, connections=None, color="white", width=1.0, **kwargs self._fig.add_trace(line_trace) return line_trace - def add_planes(self, center=(0.0, 0.0, 0.0), normal=(0.0, 0.0, 1.0), i_size=1.0, j_size=1.0, **kwargs): + def add_planes( + self, + center: Tuple[float, float, float] = (0.0, 0.0, 0.0), + normal: Tuple[float, float, float] = (0.0, 0.0, 1.0), + i_size: float = 1.0, + j_size: float = 1.0, + **kwargs + ) -> Any: """Add a plane to the scene. Parameters @@ -428,14 +448,21 @@ def add_planes(self, center=(0.0, 0.0, 0.0), normal=(0.0, 0.0, 1.0), i_size=1.0, self._fig.add_trace(plane_trace) return plane_trace - def add_text(self, text, position, font_size=12, color="white", **kwargs): + def add_text( + self, + text: str, + position: Union[Tuple[float, float], Tuple[float, float, float], str], + font_size: int = 12, + color: str = "white", + **kwargs + ) -> Any: """Add text to the scene. Parameters ---------- text : str Text string to display. - position : Tuple[float, float] + position : Union[Tuple[float, float], Tuple[float, float, float], str] Position for the text as 2D screen coordinates (x, y). Values should be between 0 and 1 for normalized coordinates, or pixel values for absolute positioning. From 260264f75ae362d8e7bcfcbf24aafa4f018f583a Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 13 Feb 2026 15:44:28 +0100 Subject: [PATCH 28/31] fix: Homogenize examples --- .../customization_api.py | 19 ---------------- .../customization_api.py | 22 +++---------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/examples/00-basic-pyvista-examples/customization_api.py b/examples/00-basic-pyvista-examples/customization_api.py index 724750095..b550370e5 100644 --- a/examples/00-basic-pyvista-examples/customization_api.py +++ b/examples/00-basic-pyvista-examples/customization_api.py @@ -36,9 +36,6 @@ from ansys.tools.visualization_interface import Plotter import pyvista as pv -###################################### -# Create a plotter with Plotly backend -# ==================================== # Create a plotter using the Plotly backend and add basic geometry. plotter = Plotter() @@ -48,9 +45,6 @@ plotter.plot(sphere) -############ -# Add points -# ========== # Add point markers to highlight specific locations. key_points = [ @@ -61,9 +55,6 @@ plotter.add_points(key_points, color='red', size=10) -########### -# Add lines -# ========= # Add line segments to show coordinate axes. # X axis @@ -79,9 +70,6 @@ plotter.add_lines(z_axis, color='blue', width=4.0) -####################### -# Add a reference plane -# ===================== # Add a plane to show a reference surface. plotter.add_planes( @@ -93,10 +81,6 @@ opacity=0.2 ) -################# -# Add text labels -# =============== -# Add text annotations using 2D normalized coordinates (0-1 range). # Scene title at the top center plotter.add_text("Customization API Example", position=(0.5, 0.95), font_size=18, color='white') @@ -105,9 +89,6 @@ plotter.add_text("Plotly Backend", position=(0.05, 0.95), font_size=12, color='lightblue') plotter.add_text("3D Visualization", position=(0.95, 0.95), font_size=12, color='lightgreen') -################# -# Show the result -# =============== # Display the visualization with all customizations. plotter.show() diff --git a/examples/01-basic-plotly-examples/customization_api.py b/examples/01-basic-plotly-examples/customization_api.py index 625751ab7..88b394271 100644 --- a/examples/01-basic-plotly-examples/customization_api.py +++ b/examples/01-basic-plotly-examples/customization_api.py @@ -37,9 +37,6 @@ from ansys.tools.visualization_interface import Plotter from ansys.tools.visualization_interface.backends.plotly.plotly_interface import PlotlyBackend -###################################### -# Create a plotter with Plotly backend -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create a plotter using the Plotly backend and add basic geometry. plotter = Plotter(backend=PlotlyBackend()) @@ -48,10 +45,6 @@ sphere = pv.Sphere(radius=1.0, center=(0, 0, 0)) plotter.plot(sphere) - -############ -# Add points -# ~~~~~~~~~~ # Add point markers to highlight specific locations. key_points = [ @@ -62,9 +55,6 @@ plotter.add_points(key_points, color='red', size=10) -########### -# Add lines -# ~~~~~~~~~ # Add line segments to show coordinate axes. # X axis @@ -80,9 +70,7 @@ plotter.add_lines(z_axis, color='blue', width=4.0) -####################### -# Add a reference plane -# ~~~~~~~~~~~~~~~~~~~~~ + # Add a plane to show a reference surface. plotter.add_planes( @@ -94,9 +82,7 @@ opacity=0.2 ) -################# -# Add text labels -# ~~~~~~~~~~~~~~~ + # Add text annotations using 2D normalized coordinates (0-1 range). # Scene title at the top center @@ -106,9 +92,7 @@ plotter.add_text("Plotly Backend", position=(0.05, 0.95), font_size=12, color='lightblue') plotter.add_text("3D Visualization", position=(0.95, 0.95), font_size=12, color='lightgreen') -################# -# Show the result -# ~~~~~~~~~~~~~~~ + # Display the visualization with all customizations. From b938010c9e72fc3befe27c3972202a04698f77a6 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 13 Feb 2026 15:51:21 +0100 Subject: [PATCH 29/31] fix: Remove unneeded check --- .../backends/pyvista/pyvista_interface.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py index 8090a3bd2..e83d80aa3 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista_interface.py @@ -451,9 +451,7 @@ def show( jupyter_backend = "html" # Enabling anti-aliasing by default on scene - # Only enable if render_window is available (not None) - if hasattr(self.scene, 'render_window') and self.scene.render_window is not None: - self.scene.enable_anti_aliasing("ssaa") + self.scene.enable_anti_aliasing("ssaa") # If screenshot is requested, set off_screen to True for the plotter if kwargs.get("screenshot") is not None: From 72f801f4fb0e3b27b6f4b2e17dc77644437666d9 Mon Sep 17 00:00:00 2001 From: Alex Fernandez Luces Date: Fri, 13 Feb 2026 16:24:26 +0100 Subject: [PATCH 30/31] fix: Feedback --- doc/source/conf.py | 2 -- examples/01-basic-plotly-examples/customization_api.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 36bcd4b8a..4d50625c8 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -117,8 +117,6 @@ "thumbnail_size": (350, 350), "remove_config_comments": True, "show_signature": False, - # Prevent module resets which can close plotters between code blocks - "reset_modules": (), } diff --git a/examples/01-basic-plotly-examples/customization_api.py b/examples/01-basic-plotly-examples/customization_api.py index 88b394271..e369e2151 100644 --- a/examples/01-basic-plotly-examples/customization_api.py +++ b/examples/01-basic-plotly-examples/customization_api.py @@ -97,4 +97,4 @@ # Uncomment to show in browser: -# plotter.show() +plotter.show() From f840d88e012ced45f4b18a79a85332fccc778cf7 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:25:36 +0000 Subject: [PATCH 31/31] chore: adding changelog file 465.maintenance.md [dependabot-skip] --- doc/changelog.d/{465.added.md => 465.maintenance.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changelog.d/{465.added.md => 465.maintenance.md} (100%) diff --git a/doc/changelog.d/465.added.md b/doc/changelog.d/465.maintenance.md similarity index 100% rename from doc/changelog.d/465.added.md rename to doc/changelog.d/465.maintenance.md