diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index 95cc2b6349..8786a5f226 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -25,12 +25,14 @@ from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier from ansys.api.geometry.v0.edges_pb2_grpc import EdgesStub -from beartype.typing import TYPE_CHECKING, List +from beartype.typing import TYPE_CHECKING, List, Union from pint import Quantity +import pyvista as pv from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.connection.conversions import grpc_curve_to_curve from ansys.geometry.core.errors import protect_grpc +from ansys.geometry.core.logger import LOG from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.misc.checks import ensure_design_is_active from ansys.geometry.core.misc.measurements import DEFAULT_UNITS @@ -40,6 +42,8 @@ if TYPE_CHECKING: # pragma: no cover from ansys.geometry.core.designer.body import Body from ansys.geometry.core.designer.face import Face + from ansys.geometry.core.shapes.curves.circle import Circle + from ansys.geometry.core.shapes.curves.ellipse import Ellipse @unique @@ -168,3 +172,58 @@ def faces(self) -> List["Face"]: ) for grpc_face in grpc_faces ] + + @property + @protect_grpc + def start_point(self) -> Point3D: + """Edge start point.""" + self._grpc_client.log.debug("Requesting edge points from server.") + point = self._edges_stub.GetStartAndEndPoints(self._grpc_id).start + return Point3D([point.x, point.y, point.z]) + + @property + @protect_grpc + def end_point(self) -> Point3D: + """Edge end point.""" + self._grpc_client.log.debug("Requesting edge points from server.") + point = self._edges_stub.GetStartAndEndPoints(self._grpc_id).end + return Point3D([point.x, point.y, point.z]) + + def to_polydata(self) -> Union[pv.PolyData, None]: + """ + Return the edge as polydata. + + This is useful to represent the edge in a PyVista plotter. + + Returns + ------- + Union[pv.PolyData, None] + Edge as polydata + """ + if ( + self._curve_type == CurveType.CURVETYPE_UNKNOWN + or self._curve_type == CurveType.CURVETYPE_LINE + ): + return pv.Line(pointa=self.start_point, pointb=self.end_point) + elif self._curve_type == CurveType.CURVETYPE_CIRCLE: + self.shape.geometry: Circle + point_a = self.shape.geometry.evaluate(0) + circle = pv.CircularArc( + center=list(self.shape.geometry.origin), + pointa=list(point_a.position), + pointb=list(point_a.position), + negative=True, + ) + return circle + + elif self._curve_type == CurveType.CURVETYPE_ELLIPSE: + self.shape.geometry: Ellipse + ellipse = pv.Ellipse( + semi_major_axis=self.shape.geometry.major_radius.magnitude, + semi_minor_axis=self.shape.geometry.minor_radius.magnitude, + ) + ellipse = ellipse.translate(list(self.shape.geometry.origin)) + return ellipse + else: + LOG.warning("NURBS and procedural edges not supported.") + return None diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index 2e87bc2bd2..e8ea50d638 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -28,13 +28,15 @@ from ansys.api.geometry.v0.faces_pb2 import CreateIsoParamCurvesRequest from ansys.api.geometry.v0.faces_pb2_grpc import FacesStub from ansys.api.geometry.v0.models_pb2 import Edge as GRPCEdge -from beartype.typing import TYPE_CHECKING, List +from beartype.typing import TYPE_CHECKING, List, Union from pint import Quantity +import pyvista as pv from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.connection.conversions import grpc_curve_to_curve, grpc_surface_to_surface from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.errors import protect_grpc +from ansys.geometry.core.logger import LOG from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.math.vector import UnitVector3D from ansys.geometry.core.misc.checks import ensure_design_is_active @@ -358,6 +360,72 @@ def __grpc_edges_to_edges(self, edges_grpc: List[GRPCEdge]) -> List[Edge]: ) return edges + def _to_polydata(self) -> Union[pv.PolyData, None]: + """ + Return the face as polydata. + + This is useful to represent the face in a PyVista plotter. + + Returns + ------- + Union[pv.PolyData, None] + Face as polydata + """ + if self.surface_type == SurfaceType.SURFACETYPE_PLANE: + # get vertices from edges + vertices = [ + vertice + for edge in self.edges + for vertice in [edge.start_point.flat, edge.end_point.flat] + ] + # TODO remove duplicate vertices + # build the PyVista face + vertices_order = [len(vertices)] + vertices_order.extend(range(len(vertices))) + + return pv.PolyData(vertices, faces=vertices_order, n_faces=1) + elif self.surface_type == SurfaceType.SURFACETYPE_CYLINDER: + cyl_pl = pv.Cylinder( + center=list(self.shape.geometry.origin), + direction=[ + self.shape.geometry.dir_x, + self.shape.geometry.dir_y, + self.shape.geometry.dir_z, + ], + radius=self.shape.geometry.radius.magnitude, + ) + return cyl_pl + elif self.surface_type == SurfaceType.SURFACETYPE_CONE: + cone_pl = pv.Cone( + center=list(self.shape.geometry.origin), + direction=[ + self.shape.geometry.dir_x, + self.shape.geometry.dir_y, + self.shape.geometry.dir_z, + ], + height=self.shape.geometry.height.magnitude, + radius=self.shape.geometry.radius.magnitude, + angle=self.shape.geometry.half_angle.magnitude, + ) + return cone_pl + elif self.surface_type == SurfaceType.SURFACETYPE_TORUS: + torus_pl = pv.ParametricTorus( + ringradius=self.shape.geometry.major_radius.magnitude, + crosssectionradius=self.shape.geometry.minor_radius.magnitude, + ) + torus_pl.translate(self.shape.geometry.origin) + return torus_pl + + elif self.surface_type == SurfaceType.SURFACETYPE_SPHERE: + sphere_pl = pv.Sphere( + center=list(self.shape.geometry.origin), + radius=self.shape.geometry.radius.magnitude, + ) + return sphere_pl + else: + LOG.warning("Cannot convert non-planar faces to polydata.") + return None + def create_isoparametric_curves( self, use_u_param: bool, parameter: float ) -> List[TrimmedCurve]: diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index 0a53140a4c..1d8a0b0d91 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -105,12 +105,22 @@ def __init__( [ids.add(beam.id) for beam in beams] [ids.add(dp.id) for dp in design_points] + self._selections = [bodies, faces, edges, beams, design_points] + + # flatten list + self._selections = [item for sublist in self._selections for item in sublist] + named_selection_request = CreateRequest(name=name, members=ids) self._grpc_client.log.debug("Requesting creation of named selection.") new_named_selection = self._named_selections_stub.Create(named_selection_request) self._id = new_named_selection.id self._name = new_named_selection.name + @property + def selections(self): + """Return all the selected elements.""" + return self._selections + @property def id(self) -> str: """ID of the named selection.""" diff --git a/src/ansys/geometry/core/plotting/plotter.py b/src/ansys/geometry/core/plotting/plotter.py index c96f4e1688..15f6acb803 100644 --- a/src/ansys/geometry/core/plotting/plotter.py +++ b/src/ansys/geometry/core/plotting/plotter.py @@ -256,13 +256,18 @@ def add_body_edges(self, body_plot: GeomObjectPlot, **plotting_options: Optional """ edge_plot_list = [] for edge in body_plot.object.edges: - line = pv.Line(edge.shape.start, edge.shape.start) - edge_actor = self.scene.add_mesh( - line, line_width=10, color=EDGE_COLOR, **plotting_options - ) - edge_actor.SetVisibility(False) - edge_plot = EdgePlot(edge_actor, edge, body_plot) - edge_plot_list.append(edge_plot) + edge_polydata = edge.to_polydata() + if edge_polydata is not None: + edge_actor = self.scene.add_mesh( + edge_polydata, + line_width=10, + color=EDGE_COLOR, + style="wireframe", + **plotting_options, + ) + edge_actor.SetVisibility(False) + edge_plot = EdgePlot(edge_actor, edge, body_plot) + edge_plot_list.append(edge_plot) body_plot.edges = edge_plot_list def add_body( diff --git a/src/ansys/geometry/core/plotting/plotter_helper.py b/src/ansys/geometry/core/plotting/plotter_helper.py index b14a5cfca0..804aea4a71 100644 --- a/src/ansys/geometry/core/plotting/plotter_helper.py +++ b/src/ansys/geometry/core/plotting/plotter_helper.py @@ -232,15 +232,14 @@ def compute_edge_object_map(self) -> Dict[pv.Actor, EdgePlot]: for object in self._geom_object_actors_map.values(): # get edges only from bodies geom_obj = object.object - if ( - isinstance(geom_obj, Body) - or isinstance(geom_obj, MasterBody) - or isinstance(geom_obj, Face) - or isinstance(geom_obj, SketchFace) - or isinstance(geom_obj, Sketch) - ): - for edge in object.edges: - self._edge_actors_map[edge.actor] = edge + """ + If ( isinstance(geom_obj, Body) or isinstance(geom_obj, MasterBody) or + isinstance(geom_obj, Face) or isinstance(geom_obj, SketchFace) + + or isinstance(geom_obj, Sketch) ): + """ + for edge in object.edges: + self._edge_actors_map[edge.actor] = edge def enable_picking(self): """Enable picking capabilities in the plotter."""