diff --git a/doc/changelog.d/1513.added.md b/doc/changelog.d/1513.added.md new file mode 100644 index 0000000000..def58b8113 --- /dev/null +++ b/doc/changelog.d/1513.added.md @@ -0,0 +1 @@ +caching bodies to avoid unnecessary object creation \ No newline at end of file diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index cb2d031e91..d1738a3fde 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -1097,6 +1097,7 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 response.master_id, copy_name, self._grpc_client, is_surface=self.is_surface ) parent._master_component.part.bodies.append(tb) + parent._clear_cached_bodies() body_id = f"{parent.id}/{tb.id}" if parent.parent_component else tb.id return Body(body_id, response.name, parent, tb) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index fa20639deb..3fe923476e 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -22,6 +22,7 @@ """Provides for managing components.""" from enum import Enum, unique +from functools import cached_property from typing import TYPE_CHECKING, Any, Optional, Union import uuid @@ -239,6 +240,11 @@ def __init__( self._master_component.occurrences.append(self) + def _clear_cached_bodies(self) -> None: + """Clear the cached bodies.""" + if "bodies" in self.__dict__: + del self.__dict__["bodies"] + @property def id(self) -> str: """ID of the component.""" @@ -259,7 +265,7 @@ def components(self) -> list["Component"]: """List of ``Component`` objects inside of the component.""" return self._components - @property + @cached_property def bodies(self) -> list[Body]: """List of ``Body`` objects inside of the component.""" bodies = [] @@ -509,6 +515,7 @@ def extrude_sketch( response = self._bodies_stub.CreateExtrudedBody(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -558,6 +565,7 @@ def sweep_sketch( response = self._bodies_stub.CreateSweepingProfile(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -605,6 +613,7 @@ def sweep_chain( response = self._bodies_stub.CreateSweepingChain(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -720,6 +729,7 @@ def extrude_face( tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -753,6 +763,7 @@ def create_sphere(self, name: str, center: Point3D, radius: Distance) -> Body: response = self._bodies_stub.CreateSphereBody(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -819,6 +830,7 @@ def create_body_from_loft_profile( response = self._bodies_stub.CreateExtrudedBodyFromLoftProfiles(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -856,6 +868,7 @@ def create_surface(self, name: str, sketch: Sketch) -> Body: tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -896,6 +909,7 @@ def create_surface_from_face(self, name: str, face: Face) -> Body: tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -936,6 +950,7 @@ def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=response.is_surface) self._master_component.part.bodies.append(tb) + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @check_input_types @@ -1136,6 +1151,7 @@ def delete_body(self, body: Body | str) -> None: # on the client side body_requested._is_alive = False self._grpc_client.log.debug(f"Body {body_requested.id} has been deleted.") + self._clear_cached_bodies() else: self._grpc_client.log.warning( f"Body {id} is not found in this component (or subcomponents)." diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index cca741203f..e89adcdd13 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -2738,3 +2738,49 @@ def test_surface_body_creation(modeler: Modeler): assert len(design.bodies) == 6 assert not body.is_surface assert body.faces[0].area.m == pytest.approx(39.4784176044 * 2) + + +def test_cached_bodies(modeler: Modeler): + """Test verifying that bodies are cached correctly. + + Whenever a new body is created, modified etc. we should make sure that the cache is updated. + """ + design = modeler.create_design("ModelingDemo") + + # Define a sketch + origin = Point3D([0, 0, 10]) + plane = Plane(origin, direction_x=[1, 0, 0], direction_y=[0, 1, 0]) + + # Create a sketch + sketch_box = Sketch(plane) + sketch_box.box(Point2D([20, 20]), 30 * UNITS.m, 30 * UNITS.m) + + sketch_cylinder = Sketch(plane) + sketch_cylinder.circle(Point2D([20, 20]), 5 * UNITS.m) + + design.extrude_sketch(name="BoxBody", sketch=sketch_box, distance=Distance(30, unit=UNITS.m)) + design.extrude_sketch( + name="CylinderBody", + sketch=sketch_cylinder, + distance=Distance(60, unit=UNITS.m), + ) + + my_bodies = design.bodies + my_bodies_2 = design.bodies + + # We should make sure that the object memory addresses are the same + for body1, body2 in zip(my_bodies, my_bodies_2): + assert body1 is body2 # We are comparing the memory addresses + assert id(body1) == id(body2) + + design.extrude_sketch( + name="CylinderBody2", + sketch=sketch_cylinder, + distance=Distance(20, unit=UNITS.m), + direction="-", + ) + my_bodies_3 = design.bodies + + for body1, body3 in zip(my_bodies, my_bodies_3): + assert body1 is not body3 + assert id(body1) != id(body3)