Skip to content
Merged
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
1 change: 1 addition & 0 deletions doc/changelog.d/1869.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
implement lazy loading of members in NamedSelection to speed up loading times when reading model
27 changes: 2 additions & 25 deletions src/ansys/geometry/core/designer/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,6 @@
from ansys.geometry.core.math.plane import Plane
from ansys.geometry.core.math.point import Point3D
from ansys.geometry.core.math.vector import UnitVector3D, Vector3D
from ansys.geometry.core.misc.auxiliary import (
get_beams_from_ids,
get_bodies_from_ids,
get_edges_from_ids,
get_faces_from_ids,
)
from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Distance
from ansys.geometry.core.misc.options import ImportOptions
Expand Down Expand Up @@ -687,6 +681,7 @@ def create_named_selection(
"""
named_selection = NamedSelection(
name,
self,
self._grpc_client,
bodies=bodies,
faces=faces,
Expand Down Expand Up @@ -1214,29 +1209,11 @@ def __read_existing_design(self) -> None:

# Create NamedSelections
for ns in response.named_selections:
result = self._named_selections_stub.Get(EntityIdentifier(id=ns.id))

# This works but is slow -- can use improvement for designs with many named selections
bodies = get_bodies_from_ids(self, [body.id for body in result.bodies])
faces = get_faces_from_ids(self, [face.id for face in result.faces])
edges = get_edges_from_ids(self, [edge.id for edge in result.edges])
beams = get_beams_from_ids(self, [beam.id.id for beam in result.beams])

design_points = []
for dp in result.design_points:
design_points.append(
DesignPoint(dp.id, "dp: " + dp.id, grpc_point_to_point3d(dp.points[0]), self)
)

new_ns = NamedSelection(
ns.name,
self,
self._grpc_client,
preexisting_id=ns.id,
bodies=bodies,
faces=faces,
edges=edges,
beams=beams,
design_points=design_points,
)
self._named_selections[new_ns.name] = new_ns

Expand Down
18 changes: 7 additions & 11 deletions src/ansys/geometry/core/designer/designpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
# SOFTWARE.
"""Module for creating and managing design points."""

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Union

from ansys.geometry.core.math.point import Point3D
from ansys.geometry.core.misc.checks import check_type, graphics_required
from ansys.geometry.core.misc.checks import graphics_required
from ansys.geometry.core.misc.units import UNITS

if TYPE_CHECKING: # pragma: no cover
Expand All @@ -44,19 +44,15 @@ class DesignPoint:
User-defined label for the design points.
points : Point3D
3D point constituting the design points.
parent_component : Component
parent_component : Component | None
Parent component to place the new design point under within the design assembly.
Its default value is None.
"""

def __init__(self, id: str, name: str, point: Point3D, parent_component: "Component"):
def __init__(
self, id: str, name: str, point: Point3D, parent_component: Union["Component", None] = None
):
"""Initialize the ``DesignPoints`` class."""
from ansys.geometry.core.designer.component import Component

check_type(id, str)
check_type(name, str)
check_type(point, Point3D)
check_type(parent_component, Component)

self._id = id
self._name = name
self._value = point
Expand Down
93 changes: 85 additions & 8 deletions src/ansys/geometry/core/designer/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,28 @@
# SOFTWARE.
"""Module for creating a named selection."""

from beartype import beartype as check_input_types
from typing import TYPE_CHECKING

from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
from ansys.api.geometry.v0.namedselections_pb2 import CreateRequest
from ansys.api.geometry.v0.namedselections_pb2_grpc import NamedSelectionsStub
from ansys.geometry.core.connection.client import GrpcClient
from ansys.geometry.core.connection.conversions import grpc_point_to_point3d
from ansys.geometry.core.designer.beam import Beam
from ansys.geometry.core.designer.body import Body
from ansys.geometry.core.designer.designpoint import DesignPoint
from ansys.geometry.core.designer.edge import Edge
from ansys.geometry.core.designer.face import Face
from ansys.geometry.core.errors import protect_grpc
from ansys.geometry.core.misc.auxiliary import (
get_beams_from_ids,
get_bodies_from_ids,
get_edges_from_ids,
get_faces_from_ids,
)

if TYPE_CHECKING:
from ansys.geometry.core.designer.design import Design


class NamedSelection:
Expand All @@ -46,6 +57,8 @@ class NamedSelection:
----------
name : str
User-defined name for the named selection.
design : Design
Design instance to which the named selection belongs.
grpc_client : GrpcClient
Active supporting Geometry service instance for design modeling.
bodies : list[Body], default: None
Expand All @@ -61,10 +74,10 @@ class NamedSelection:
"""

@protect_grpc
@check_input_types
def __init__(
self,
name: str,
design: "Design",
grpc_client: GrpcClient,
bodies: list[Body] | None = None,
faces: list[Face] | None = None,
Expand All @@ -75,6 +88,7 @@ def __init__(
):
"""Initialize the ``NamedSelection`` class."""
self._name = name
self._design = design
self._grpc_client = grpc_client
self._named_selections_stub = NamedSelectionsStub(self._grpc_client.channel)

Expand All @@ -97,19 +111,26 @@ def __init__(
self._beams = beams
self._design_points = design_points

# Store ids for later use... when verifying if the NS changed.
self._ids_cached = {
"bodies": [body.id for body in bodies],
"faces": [face.id for face in faces],
"edges": [edge.id for edge in edges],
"beams": [beam.id for beam in beams],
"design_points": [dp.id for dp in design_points],
}

if preexisting_id:
self._id = preexisting_id
return

# All ids should be unique - no duplicated values
ids = set()

# Loop over bodies, faces and edges
[ids.add(body.id) for body in bodies]
[ids.add(face.id) for face in faces]
[ids.add(edge.id) for edge in edges]
[ids.add(beam.id) for beam in beams]
[ids.add(dp.id) for dp in design_points]
# Loop over all entities to get their ids
for value in self._ids_cached.values():
for entity_id in value:
ids.add(entity_id)

named_selection_request = CreateRequest(name=name, members=ids)
self._grpc_client.log.debug("Requesting creation of named selection.")
Expand All @@ -129,28 +150,84 @@ def name(self) -> str:
@property
def bodies(self) -> list[Body]:
"""All bodies in the named selection."""
self.__verify_ns()
if self._bodies is None:
# Get all bodies from the named selection
self._bodies = get_bodies_from_ids(self._design, self._ids_cached["bodies"])

return self._bodies

@property
def faces(self) -> list[Face]:
"""All faces in the named selection."""
self.__verify_ns()
if self._faces is None:
# Get all faces from the named selection
self._faces = get_faces_from_ids(self._design, self._ids_cached["faces"])

return self._faces

@property
def edges(self) -> list[Edge]:
"""All edges in the named selection."""
self.__verify_ns()
if self._edges is None:
# Get all edges from the named selection
self._edges = get_edges_from_ids(self._design, self._ids_cached["edges"])

return self._edges

@property
def beams(self) -> list[Beam]:
"""All beams in the named selection."""
self.__verify_ns()
if self._beams is None:
# Get all beams from the named selection
self._beams = get_beams_from_ids(self._design, self._ids_cached["beams"])

return self._beams

@property
def design_points(self) -> list[DesignPoint]:
"""All design points in the named selection."""
self.__verify_ns()
if self._design_points is None:
# Get all design points from the named selection
self._design_points = [
DesignPoint(dp_id, f"dp: {dp_id}", grpc_point_to_point3d(dp_point))
for dp_id, dp_point in self._ids_cached["design_points"]
]

return self._design_points

def __verify_ns(self) -> None:
"""Verify that the contents of the named selection are up to date."""
if self._grpc_client.backend_version < (25, 2, 0):
self._grpc_client.log.warning(
"Accessing members of named selections is only"
" consistent starting in version 2025 R2."
)
return

# Get all entities from the named selection
resp = self._named_selections_stub.Get(EntityIdentifier(id=self._id))

# Check if the named selection has changed
ids = {
"bodies": [body.id for body in resp.bodies],
"faces": [face.id for face in resp.faces],
"edges": [edge.id for edge in resp.edges],
"beams": [beam.id.id for beam in resp.beams],
"design_points": [(dp.id, dp.points[0]) for dp in resp.design_points],
}

for key in ids:
if ids[key] != self._ids_cached[key]:
# Clear the cache for that specific entity
setattr(self, f"_{key}", None)
# Update the cache
self._ids_cached[key] = ids[key]

def __repr__(self) -> str:
"""Represent the ``NamedSelection`` as a string."""
lines = [f"ansys.geometry.core.designer.selection.NamedSelection {hex(id(self))}"]
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,12 @@ def test_named_selection_contents(modeler: Modeler):
assert len(ns.edges) == 1
assert ns.edges[0].id == edge.id

assert len(ns.beams) == 1
# TODO: When named selection is created using beams...
# the beams are not being added to the named selection for some reason. We cannot
# retrieve them from the NamedSelectionStub.Get() method. This is a bug.
# https://github.com/ansys/pyansys-geometry/issues/1868
# assert len(ns.beams) == 1 # This should be 1

assert len(ns.design_points) == 0


Expand Down
Loading