Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add polydata conversors to edges and faces #759

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/ansys/geometry/core/designer/edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is useful to represent the edge in a PyVista plotter.
This method 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
70 changes: 69 additions & 1 deletion src/ansys/geometry/core/designer/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is useful to represent the face in a PyVista plotter.
This method 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]:
Expand Down
10 changes: 10 additions & 0 deletions src/ansys/geometry/core/designer/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
19 changes: 12 additions & 7 deletions src/ansys/geometry/core/plotting/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
17 changes: 8 additions & 9 deletions src/ansys/geometry/core/plotting/plotter_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
Loading